From efb5fc9fef693085e1eab22d84bd250f2bc241d6 Mon Sep 17 00:00:00 2001 From: Robert Gemmell Date: Mon, 23 Sep 2013 23:26:35 +0000 Subject: QPID-5159: move the entire broker dir to broker-core, no other changes, fixups in next commit git-svn-id: https://svn.apache.org/repos/asf/qpid/trunk@1525731 13f79535-47bb-0310-9956-ffa450edef68 --- qpid/java/broker-core/bin/qpid-passwd | 35 + qpid/java/broker-core/bin/qpid-server | 56 + qpid/java/broker-core/bin/qpid-server.bat | 201 ++ qpid/java/broker-core/bin/qpid.stop | 178 ++ qpid/java/broker-core/bin/qpid.stopall | 27 + qpid/java/broker-core/build-generate-sources.xml | 97 + qpid/java/broker-core/build.xml | 87 + qpid/java/broker-core/etc/broker_example.acl | 131 ++ qpid/java/broker-core/etc/groups | 29 + qpid/java/broker-core/etc/log4j.xml | 123 + qpid/java/broker-core/etc/md5passwd | 23 + qpid/java/broker-core/etc/passwd | 25 + qpid/java/broker-core/python-test.xml | 56 + qpid/java/broker-core/scripts/resetAlerting.sh | 116 + qpid/java/broker-core/src/main/java/broker.bnd | 26 + .../src/main/java/fallback-log4j.properties | 24 + .../apache/log4j/QpidCompositeRollingAppender.java | 1206 ++++++++++ .../main/java/org/apache/qpid/server/Broker.java | 279 +++ .../java/org/apache/qpid/server/BrokerOptions.java | 376 ++++ .../src/main/java/org/apache/qpid/server/Main.java | 381 ++++ .../qpid/server/TransactionTimeoutHelper.java | 102 + .../org/apache/qpid/server/binding/Binding.java | 127 ++ .../BrokerConfigurationStoreCreator.java | 82 + .../server/configuration/BrokerProperties.java | 81 + .../server/configuration/ConfigurationEntry.java | 203 ++ .../configuration/ConfigurationEntryStore.java | 87 + .../configuration/ConfiguredObjectRecoverer.java | 28 + .../configuration/ExchangeConfiguration.java | 58 + .../IllegalConfigurationException.java | 37 + .../server/configuration/QueueConfiguration.java | 211 ++ .../server/configuration/RecovererProvider.java | 28 + .../configuration/VirtualHostConfiguration.java | 301 +++ .../configuration/XmlConfigurationUtilities.java | 93 + .../plugins/AbstractConfiguration.java | 344 +++ .../startup/AccessControlProviderRecoverer.java | 54 + .../startup/AuthenticationProviderRecoverer.java | 95 + .../configuration/startup/BrokerRecoverer.java | 207 ++ .../startup/DefaultRecovererProvider.java | 136 ++ .../startup/GroupProviderRecoverer.java | 53 + .../configuration/startup/KeyStoreRecoverer.java | 40 + .../configuration/startup/PluginRecoverer.java | 66 + .../configuration/startup/PortRecoverer.java | 52 + .../startup/PreferencesProviderRecoverer.java | 41 + .../configuration/startup/RecovererHelper.java | 44 + .../configuration/startup/StoreUpgrader.java | 86 + .../configuration/startup/TrustStoreRecoverer.java | 40 + .../startup/VirtualHostRecoverer.java | 51 + .../store/ConfigurationEntryStoreUtil.java | 91 + .../store/JsonConfigurationEntryStore.java | 128 ++ .../store/ManagementModeStoreHandler.java | 353 +++ .../store/MemoryConfigurationEntryStore.java | 709 ++++++ .../store/StoreConfigurationChangeListener.java | 219 ++ .../factory/JsonConfigurationStoreFactory.java | 42 + .../factory/MemoryConfigurationStoreFactory.java | 42 + .../updater/ChangeAttributesTask.java | 62 + .../configuration/updater/ChangeStateTask.java | 67 + .../configuration/updater/CreateChildTask.java | 78 + .../configuration/updater/SetAttributeTask.java | 74 + .../server/configuration/updater/TaskExecutor.java | 324 +++ .../qpid/server/connection/ConnectionRegistry.java | 131 ++ .../server/connection/IConnectionRegistry.java | 54 + .../qpid/server/exchange/AbstractExchange.java | 679 ++++++ .../qpid/server/exchange/DefaultExchange.java | 346 +++ .../server/exchange/DefaultExchangeFactory.java | 145 ++ .../server/exchange/DefaultExchangeRegistry.java | 219 ++ .../qpid/server/exchange/DirectExchange.java | 215 ++ .../qpid/server/exchange/DirectExchangeType.java | 53 + .../org/apache/qpid/server/exchange/Exchange.java | 168 ++ .../qpid/server/exchange/ExchangeFactory.java | 43 + .../server/exchange/ExchangeInUseException.java | 45 + .../qpid/server/exchange/ExchangeInitialiser.java | 53 + .../qpid/server/exchange/ExchangeReferrer.java | 26 + .../qpid/server/exchange/ExchangeRegistry.java | 69 + .../qpid/server/exchange/FanoutExchange.java | 224 ++ .../qpid/server/exchange/FanoutExchangeType.java | 52 + .../apache/qpid/server/exchange/FilterSupport.java | 218 ++ .../qpid/server/exchange/HeadersBinding.java | 284 +++ .../qpid/server/exchange/HeadersExchange.java | 166 ++ .../qpid/server/exchange/HeadersExchangeType.java | 53 + .../apache/qpid/server/exchange/TopicExchange.java | 262 +++ .../qpid/server/exchange/TopicExchangeType.java | 53 + .../server/exchange/topic/TopicExchangeResult.java | 209 ++ .../exchange/topic/TopicMatcherDFAState.java | 310 +++ .../server/exchange/topic/TopicMatcherResult.java | 25 + .../server/exchange/topic/TopicNormalizer.java | 92 + .../qpid/server/exchange/topic/TopicParser.java | 456 ++++ .../qpid/server/exchange/topic/TopicWord.java | 39 + .../server/exchange/topic/TopicWordDictionary.java | 56 + .../apache/qpid/server/filter/FilterManager.java | 37 + .../qpid/server/filter/FilterManagerFactory.java | 93 + .../qpid/server/filter/JMSSelectorFilter.java | 155 ++ .../apache/qpid/server/filter/MessageFilter.java | 28 + .../qpid/server/filter/NoConsumerFilter.java | 49 + .../qpid/server/filter/SimpleFilterManager.java | 100 + .../server/flow/AbstractFlowCreditManager.java | 74 + .../qpid/server/flow/BytesOnlyCreditManager.java | 87 + .../apache/qpid/server/flow/FlowCreditManager.java | 46 + .../qpid/server/flow/LimitlessCreditManager.java | 53 + .../server/flow/MessageAndBytesCreditManager.java | 90 + .../qpid/server/flow/MessageOnlyCreditManager.java | 86 + .../qpid/server/flow/Pre0_10CreditManager.java | 192 ++ .../server/logging/AbstractRootMessageLogger.java | 58 + .../logging/CompositeStartupMessageLogger.java | 51 + .../qpid/server/logging/Log4jMessageLogger.java | 69 + .../org/apache/qpid/server/logging/LogActor.java | 66 + .../org/apache/qpid/server/logging/LogMessage.java | 26 + .../apache/qpid/server/logging/LogRecorder.java | 243 ++ .../org/apache/qpid/server/logging/LogSubject.java | 36 + .../qpid/server/logging/NullRootMessageLogger.java | 47 + .../qpid/server/logging/RootMessageLogger.java | 75 + .../server/logging/SystemOutMessageLogger.java | 51 + .../server/logging/actors/AMQPChannelActor.java | 61 + .../server/logging/actors/AMQPConnectionActor.java | 53 + .../qpid/server/logging/actors/AbstractActor.java | 71 + .../logging/actors/AbstractManagementActor.java | 68 + .../qpid/server/logging/actors/BrokerActor.java | 53 + .../qpid/server/logging/actors/CurrentActor.java | 146 ++ .../qpid/server/logging/actors/GenericActor.java | 84 + .../server/logging/actors/HttpManagementActor.java | 62 + .../server/logging/actors/ManagementActor.java | 99 + .../qpid/server/logging/actors/QueueActor.java | 53 + .../server/logging/actors/SubscriptionActor.java | 46 + .../logging/log4j/LoggingFacadeException.java | 45 + .../logging/log4j/LoggingManagementFacade.java | 579 +++++ .../messages/Binding_logmessages.properties | 22 + .../logging/messages/Broker_logmessages.properties | 50 + .../messages/Channel_logmessages.properties | 40 + .../messages/ConfigStore_logmessages.properties | 26 + .../messages/Connection_logmessages.properties | 26 + .../messages/Exchange_logmessages.properties | 25 + .../ManagementConsole_logmessages.properties | 38 + .../messages/MessageStore_logmessages.properties | 30 + .../logging/messages/Queue_logmessages.properties | 26 + .../messages/Subscription_logmessages.properties | 24 + .../messages/TransactionLog_logmessages.properties | 38 + .../messages/VirtualHost_logmessages.properties | 28 + .../logging/subjects/AbstractLogSubject.java | 72 + .../server/logging/subjects/BindingLogSubject.java | 51 + .../server/logging/subjects/ChannelLogSubject.java | 56 + .../logging/subjects/ConnectionLogSubject.java | 106 + .../logging/subjects/ExchangeLogSubject.java | 37 + .../server/logging/subjects/LogSubjectFormat.java | 118 + .../logging/subjects/MessageStoreLogSubject.java | 37 + .../server/logging/subjects/QueueLogSubject.java | 37 + .../logging/subjects/SubscriptionLogSubject.java | 55 + .../qpid/server/message/AMQMessageHeader.java | 61 + .../server/message/AbstractServerMessageImpl.java | 111 + .../qpid/server/message/EnqueableMessage.java | 30 + .../apache/qpid/server/message/InboundMessage.java | 37 + .../qpid/server/message/MessageContentSource.java | 32 + .../qpid/server/message/MessageReference.java | 58 + .../apache/qpid/server/message/ServerMessage.java | 54 + .../qpid/server/model/AccessControlProvider.java | 56 + .../org/apache/qpid/server/model/Attribute.java | 199 ++ .../qpid/server/model/AuthenticationMethod.java | 33 + .../qpid/server/model/AuthenticationProvider.java | 80 + .../java/org/apache/qpid/server/model/Binding.java | 75 + .../java/org/apache/qpid/server/model/Broker.java | 190 ++ .../server/model/ConfigurationChangeListener.java | 39 + .../apache/qpid/server/model/ConfiguredObject.java | 262 +++ .../qpid/server/model/ConfiguredObjectFinder.java | 38 + .../org/apache/qpid/server/model/Connection.java | 112 + .../org/apache/qpid/server/model/Consumer.java | 73 + .../java/org/apache/qpid/server/model/Event.java | 27 + .../org/apache/qpid/server/model/EventType.java | 60 + .../org/apache/qpid/server/model/Exchange.java | 91 + .../java/org/apache/qpid/server/model/Group.java | 52 + .../org/apache/qpid/server/model/GroupMember.java | 52 + .../apache/qpid/server/model/GroupProvider.java | 55 + .../model/IllegalStateTransitionException.java | 43 + .../server/model/IntegrityViolationException.java | 37 + .../org/apache/qpid/server/model/KeyStore.java | 72 + .../apache/qpid/server/model/LifetimePolicy.java | 27 + .../java/org/apache/qpid/server/model/Model.java | 124 + ...rdCredentialManagingAuthenticationProvider.java | 46 + .../java/org/apache/qpid/server/model/Plugin.java | 52 + .../java/org/apache/qpid/server/model/Port.java | 111 + .../qpid/server/model/PreferencesProvider.java | 89 + .../org/apache/qpid/server/model/Protocol.java | 113 + .../org/apache/qpid/server/model/Publisher.java | 25 + .../java/org/apache/qpid/server/model/Queue.java | 155 ++ .../server/model/QueueNotificationListener.java | 28 + .../org/apache/qpid/server/model/QueueType.java | 29 + .../java/org/apache/qpid/server/model/Session.java | 82 + .../java/org/apache/qpid/server/model/State.java | 32 + .../org/apache/qpid/server/model/Statistics.java | 26 + .../org/apache/qpid/server/model/Transport.java | 51 + .../org/apache/qpid/server/model/TrustStore.java | 73 + .../apache/qpid/server/model/UUIDGenerator.java | 100 + .../java/org/apache/qpid/server/model/User.java | 65 + .../org/apache/qpid/server/model/VirtualHost.java | 171 ++ .../apache/qpid/server/model/VirtualHostAlias.java | 37 + .../qpid/server/model/adapter/AbstractAdapter.java | 477 ++++ .../model/adapter/AbstractKeyStoreAdapter.java | 188 ++ .../model/adapter/AbstractPluginAdapter.java | 191 ++ .../adapter/AccessControlProviderAdapter.java | 300 +++ .../adapter/AccessControlProviderFactory.java | 90 + .../qpid/server/model/adapter/AmqpPortAdapter.java | 277 +++ .../adapter/AuthenticationProviderAdapter.java | 826 +++++++ .../adapter/AuthenticationProviderFactory.java | 111 + .../qpid/server/model/adapter/BindingAdapter.java | 240 ++ .../qpid/server/model/adapter/BrokerAdapter.java | 1267 +++++++++++ .../server/model/adapter/ConnectionAdapter.java | 322 +++ .../qpid/server/model/adapter/ConsumerAdapter.java | 242 ++ .../qpid/server/model/adapter/ExchangeAdapter.java | 444 ++++ .../adapter/FileSystemPreferencesProvider.java | 592 +++++ .../FileSystemPreferencesProviderFactory.java | 53 + .../server/model/adapter/GroupProviderAdapter.java | 710 ++++++ .../server/model/adapter/GroupProviderFactory.java | 118 + .../qpid/server/model/adapter/KeyStoreAdapter.java | 254 +++ .../qpid/server/model/adapter/NoStatistics.java | 46 + .../qpid/server/model/adapter/PortAdapter.java | 559 +++++ .../qpid/server/model/adapter/PortFactory.java | 192 ++ .../model/adapter/PreferencesProviderCreator.java | 66 + .../qpid/server/model/adapter/QueueAdapter.java | 816 +++++++ .../qpid/server/model/adapter/SessionAdapter.java | 293 +++ .../server/model/adapter/StatisticsAdapter.java | 67 + .../server/model/adapter/TrustStoreAdapter.java | 255 +++ .../server/model/adapter/VirtualHostAdapter.java | 1273 +++++++++++ .../model/adapter/VirtualHostAliasAdapter.java | 150 ++ .../qpid/server/plugin/AccessControlFactory.java | 51 + .../plugin/AuthenticationManagerFactory.java | 58 + .../server/plugin/ConfigurationStoreFactory.java | 48 + .../plugin/DurableConfigurationStoreFactory.java | 35 + .../apache/qpid/server/plugin/ExchangeType.java | 38 + .../qpid/server/plugin/GroupManagerFactory.java | 51 + .../plugin/JDBCConnectionProviderFactory.java | 79 + .../qpid/server/plugin/MessageConverter.java | 32 + .../qpid/server/plugin/MessageMetaDataType.java | 42 + .../qpid/server/plugin/MessageStoreFactory.java | 36 + .../org/apache/qpid/server/plugin/Pluggable.java | 25 + .../apache/qpid/server/plugin/PluginFactory.java | 32 + .../server/plugin/PreferencesProviderFactory.java | 12 + .../qpid/server/plugin/ProtocolEngineCreator.java | 35 + .../qpid/server/plugin/QpidServiceLoader.java | 72 + .../qpid/server/plugin/VirtualHostFactory.java | 92 + .../qpid/server/protocol/AMQConnectionModel.java | 97 + .../qpid/server/protocol/AMQSessionModel.java | 91 + .../qpid/server/protocol/AmqpProtocolVersion.java | 23 + .../org/apache/qpid/server/protocol/LinkModel.java | 25 + .../apache/qpid/server/protocol/LinkRegistry.java | 79 + .../server/protocol/MessageConverterRegistry.java | 59 + .../protocol/MultiVersionProtocolEngine.java | 680 ++++++ .../MultiVersionProtocolEngineFactory.java | 89 + .../apache/qpid/server/queue/AMQPriorityQueue.java | 46 + .../org/apache/qpid/server/queue/AMQQueue.java | 331 +++ .../apache/qpid/server/queue/AMQQueueFactory.java | 542 +++++ .../org/apache/qpid/server/queue/BaseQueue.java | 44 + .../apache/qpid/server/queue/ConflationQueue.java | 48 + .../qpid/server/queue/ConflationQueueList.java | 256 +++ .../qpid/server/queue/DefaultQueueRegistry.java | 129 ++ .../org/apache/qpid/server/queue/Filterable.java | 32 + .../qpid/server/queue/InboundMessageAdapter.java | 71 + .../qpid/server/queue/NotificationCheck.java | 150 ++ .../apache/qpid/server/queue/OutOfOrderQueue.java | 70 + .../qpid/server/queue/PriorityQueueList.java | 217 ++ .../qpid/server/queue/QueueArgumentsConverter.java | 152 ++ .../org/apache/qpid/server/queue/QueueContext.java | 64 + .../org/apache/qpid/server/queue/QueueEntry.java | 253 +++ .../apache/qpid/server/queue/QueueEntryImpl.java | 517 +++++ .../qpid/server/queue/QueueEntryIterator.java | 30 + .../apache/qpid/server/queue/QueueEntryList.java | 40 + .../qpid/server/queue/QueueEntryListFactory.java | 26 + .../qpid/server/queue/QueueEntryVisitor.java | 22 + .../org/apache/qpid/server/queue/QueueFactory.java | 47 + .../apache/qpid/server/queue/QueueRegistry.java | 51 + .../org/apache/qpid/server/queue/QueueRunner.java | 127 ++ .../apache/qpid/server/queue/SimpleAMQQueue.java | 2289 +++++++++++++++++++ .../qpid/server/queue/SimpleQueueEntryImpl.java | 78 + .../qpid/server/queue/SimpleQueueEntryList.java | 205 ++ .../org/apache/qpid/server/queue/SortedQueue.java | 58 + .../qpid/server/queue/SortedQueueEntryImpl.java | 143 ++ .../qpid/server/queue/SortedQueueEntryList.java | 659 ++++++ .../server/queue/SortedQueueEntryListFactory.java | 38 + .../apache/qpid/server/queue/SubFlushRunner.java | 119 + .../qpid/server/registry/ApplicationRegistry.java | 355 +++ .../qpid/server/registry/IApplicationRegistry.java | 38 + .../apache/qpid/server/security/AccessControl.java | 66 + .../qpid/server/security/AuthorizationHolder.java | 49 + .../org/apache/qpid/server/security/Result.java | 46 + .../qpid/server/security/SecurityManager.java | 643 ++++++ .../qpid/server/security/SubjectCreator.java | 162 ++ .../access/FileAccessControlProviderConstants.java | 27 + .../server/security/access/ObjectProperties.java | 336 +++ .../qpid/server/security/access/ObjectType.java | 107 + .../qpid/server/security/access/Operation.java | 56 + .../security/access/OperationLoggingDetails.java | 53 + .../qpid/server/security/access/Permission.java | 47 + .../security/auth/AuthenticatedPrincipal.java | 127 ++ .../server/security/auth/AuthenticationResult.java | 145 ++ .../security/auth/SubjectAuthenticationResult.java | 76 + .../server/security/auth/UsernamePrincipal.java | 77 + .../AbstractPasswordFilePrincipalDatabase.java | 465 ++++ .../Base64MD5PasswordFilePrincipalDatabase.java | 156 ++ .../server/security/auth/database/HashedUser.java | 194 ++ .../security/auth/database/PasswordPrincipal.java | 40 + .../PlainPasswordFilePrincipalDatabase.java | 146 ++ .../server/security/auth/database/PlainUser.java | 109 + .../security/auth/database/PrincipalDatabase.java | 114 + .../auth/jmx/JMXPasswordAuthenticator.java | 136 ++ ...bstractPrincipalDatabaseAuthManagerFactory.java | 74 + .../manager/AnonymousAuthenticationManager.java | 125 ++ .../AnonymousAuthenticationManagerFactory.java | 59 + .../auth/manager/AuthenticationManager.java | 94 + ...D5PasswordFileAuthenticationManagerFactory.java | 51 + .../manager/ExternalAuthenticationManager.java | 114 + .../ExternalAuthenticationManagerFactory.java | 70 + ...icationProviderAttributeDescriptions.properties | 19 + .../manager/KerberosAuthenticationManager.java | 143 ++ .../KerberosAuthenticationManagerFactory.java | 59 + ...icationProviderAttributeDescriptions.properties | 19 + ...inPasswordFileAuthenticationManagerFactory.java | 50 + .../PrincipalDatabaseAuthenticationManager.java | 163 ++ .../auth/manager/SimpleAuthenticationManager.java | 214 ++ .../manager/SimpleLDAPAuthenticationManager.java | 317 +++ .../SimpleLDAPAuthenticationManagerFactory.java | 94 + ...icationProviderAttributeDescriptions.properties | 23 + .../sasl/AuthenticationProviderInitialiser.java | 39 + .../auth/sasl/UsernamePasswordInitialiser.java | 103 + .../auth/sasl/amqplain/AmqPlainInitialiser.java | 31 + .../auth/sasl/amqplain/AmqPlainSaslServer.java | 132 ++ .../sasl/amqplain/AmqPlainSaslServerFactory.java | 60 + .../auth/sasl/anonymous/AnonymousSaslServer.java | 78 + .../sasl/anonymous/AnonymousSaslServerFactory.java | 61 + .../sasl/crammd5/CRAMMD5HashedInitialiser.java | 37 + .../auth/sasl/crammd5/CRAMMD5HashedSaslServer.java | 105 + .../sasl/crammd5/CRAMMD5HashedServerFactory.java | 60 + .../auth/sasl/crammd5/CRAMMD5HexInitialiser.java | 149 ++ .../auth/sasl/crammd5/CRAMMD5HexSaslServer.java | 105 + .../auth/sasl/crammd5/CRAMMD5HexServerFactory.java | 60 + .../auth/sasl/crammd5/CRAMMD5Initialiser.java | 33 + .../auth/sasl/external/ExternalSaslServer.java | 125 ++ .../security/auth/sasl/plain/PlainInitialiser.java | 31 + .../auth/sasl/plain/PlainPasswordCallback.java | 80 + .../security/auth/sasl/plain/PlainSaslServer.java | 161 ++ .../auth/sasl/plain/PlainSaslServerFactory.java | 60 + .../server/security/group/FileGroupDatabase.java | 287 +++ .../server/security/group/FileGroupManager.java | 239 ++ .../security/group/FileGroupManagerFactory.java | 79 + ...leGroupProviderAttributeDescriptions.properties | 19 + .../qpid/server/security/group/GroupDatabase.java | 34 + .../qpid/server/security/group/GroupManager.java | 48 + .../qpid/server/security/group/GroupPrincipal.java | 100 + .../qpid/server/stats/StatisticsCounter.java | 157 ++ .../qpid/server/stats/StatisticsGatherer.java | 106 + .../AbstractDurableConfiguredObjectRecoverer.java | 77 + .../server/store/AbstractJDBCMessageStore.java | 2363 ++++++++++++++++++++ .../server/store/AbstractMemoryMessageStore.java | 147 ++ .../server/store/ConfigurationRecoveryHandler.java | 41 + .../qpid/server/store/ConfiguredObjectRecord.java | 88 + .../qpid/server/store/DependencyListener.java | 28 + .../store/DurableConfigurationRecoverer.java | 242 ++ .../server/store/DurableConfigurationStore.java | 99 + .../store/DurableConfigurationStoreCreator.java | 78 + .../store/DurableConfigurationStoreHelper.java | 145 ++ .../store/DurableConfigurationStoreUpgrader.java | 35 + .../store/DurableConfiguredObjectRecoverer.java | 33 + .../java/org/apache/qpid/server/store/Event.java | 42 + .../apache/qpid/server/store/EventListener.java | 25 + .../org/apache/qpid/server/store/EventManager.java | 63 + .../apache/qpid/server/store/HAMessageStore.java | 29 + .../qpid/server/store/JsonFileConfigStore.java | 513 +++++ .../server/store/JsonFileConfigStoreFactory.java | 52 + .../server/store/MessageMetaDataTypeRegistry.java | 65 + .../org/apache/qpid/server/store/MessageStore.java | 74 + .../server/store/MessageStoreClosedException.java | 38 + .../qpid/server/store/MessageStoreConstants.java | 31 + .../qpid/server/store/MessageStoreCreator.java | 79 + .../server/store/MessageStoreRecoveryHandler.java | 33 + .../apache/qpid/server/store/NonNullUpgrader.java | 62 + .../apache/qpid/server/store/NullMessageStore.java | 115 + .../org/apache/qpid/server/store/NullUpgrader.java | 58 + .../server/store/OperationalLoggingListener.java | 86 + .../java/org/apache/qpid/server/store/State.java | 47 + .../org/apache/qpid/server/store/StateManager.java | 150 ++ .../qpid/server/store/StorableMessageMetaData.java | 38 + .../org/apache/qpid/server/store/StoreContext.java | 73 + .../org/apache/qpid/server/store/StoreFuture.java | 40 + .../qpid/server/store/StoredMemoryMessage.java | 139 ++ .../apache/qpid/server/store/StoredMessage.java | 40 + .../org/apache/qpid/server/store/Transaction.java | 81 + .../apache/qpid/server/store/TransactionLog.java | 26 + .../store/TransactionLogRecoveryHandler.java | 42 + .../qpid/server/store/TransactionLogResource.java | 28 + .../qpid/server/store/UnresolvedDependency.java | 31 + .../apache/qpid/server/store/UnresolvedObject.java | 28 + .../apache/qpid/server/store/UpgraderProvider.java | 26 + .../qpid/server/store/jdbc/ConnectionProvider.java | 32 + .../AssignedSubscriptionMessageGroupManager.java | 158 ++ .../server/subscription/ClientDeliveryMethod.java | 29 + .../DefinedGroupMessageGroupManager.java | 270 +++ .../server/subscription/MessageGroupManager.java | 41 + .../server/subscription/RecordDeliveryMethod.java | 28 + .../qpid/server/subscription/Subscription.java | 124 + .../qpid/server/subscription/SubscriptionList.java | 280 +++ .../qpid/server/txn/AlreadyKnownDtxException.java | 32 + .../server/txn/AsyncAutoCommitTransaction.java | 360 +++ .../qpid/server/txn/AutoCommitTransaction.java | 282 +++ .../qpid/server/txn/DistributedTransaction.java | 249 +++ .../java/org/apache/qpid/server/txn/DtxBranch.java | 413 ++++ .../org/apache/qpid/server/txn/DtxException.java | 44 + .../qpid/server/txn/DtxNotSelectedException.java | 30 + .../org/apache/qpid/server/txn/DtxRegistry.java | 334 +++ .../server/txn/IncorrectDtxStateException.java | 32 + .../qpid/server/txn/JoinAndResumeDtxException.java | 32 + .../apache/qpid/server/txn/LocalTransaction.java | 483 ++++ .../qpid/server/txn/NotAssociatedDtxException.java | 32 + .../qpid/server/txn/RollbackOnlyDtxException.java | 32 + .../apache/qpid/server/txn/ServerTransaction.java | 122 + .../server/txn/SuspendAndFailDtxException.java | 32 + .../qpid/server/txn/TimeoutDtxException.java | 32 + .../qpid/server/txn/UnknownDtxBranchException.java | 32 + .../qpid/server/util/ByteBufferOutputStream.java | 46 + .../apache/qpid/server/util/MapJsonSerializer.java | 69 + .../apache/qpid/server/util/MapValueConverter.java | 412 ++++ .../qpid/server/util/ParameterizedTypeImpl.java | 73 + .../qpid/server/util/ResourceBundleLoader.java | 49 + .../org/apache/qpid/server/util/StringUtil.java | 44 + .../server/virtualhost/AbstractVirtualHost.java | 930 ++++++++ .../qpid/server/virtualhost/BindingRecoverer.java | 177 ++ .../virtualhost/DefaultUpgraderProvider.java | 266 +++ .../virtualhost/ExchangeExistsException.java | 39 + .../virtualhost/ExchangeIsAlternateException.java | 30 + .../qpid/server/virtualhost/ExchangeRecoverer.java | 102 + .../qpid/server/virtualhost/HouseKeepingTask.java | 84 + .../server/virtualhost/ManagedVirtualHost.java | 44 + .../qpid/server/virtualhost/QueueRecoverer.java | 157 ++ .../virtualhost/RequiredExchangeException.java | 30 + .../virtualhost/ReservedExchangeNameException.java | 38 + .../server/virtualhost/StandardVirtualHost.java | 135 ++ .../virtualhost/StandardVirtualHostFactory.java | 118 + .../org/apache/qpid/server/virtualhost/State.java | 31 + .../virtualhost/UnknownExchangeException.java | 38 + .../qpid/server/virtualhost/VirtualHost.java | 126 ++ .../VirtualHostConfigRecoveryHandler.java | 375 ++++ .../virtualhost/VirtualHostFactoryRegistry.java | 65 + .../server/virtualhost/VirtualHostListener.java | 41 + .../server/virtualhost/VirtualHostRegistry.java | 95 + .../virtualhost/plugins/QueueExistsException.java | 40 + .../org/apache/qpid/tools/security/Passwd.java | 70 + ...qpid.server.plugin.AuthenticationManagerFactory | 24 + ...he.qpid.server.plugin.ConfigurationStoreFactory | 20 + ....server.plugin.DurableConfigurationStoreFactory | 19 + .../org.apache.qpid.server.plugin.ExchangeType | 22 + ...g.apache.qpid.server.plugin.GroupManagerFactory | 19 + ...pid.server.plugin.JDBCConnectionProviderFactory | 19 + ...e.qpid.server.plugin.PreferencesProviderFactory | 19 + ...rg.apache.qpid.server.plugin.VirtualHostFactory | 19 + .../src/main/resources/initial-config.json | 68 + .../org/apache/qpid/server/BrokerOptionsTest.java | 329 +++ .../test/java/org/apache/qpid/server/MainTest.java | 284 +++ .../org/apache/qpid/server/SelectorParserTest.java | 140 ++ .../qpid/server/TransactionTimeoutHelperTest.java | 230 ++ .../BrokerConfigurationStoreCreatorTest.java | 165 ++ .../server/configuration/BrokerPropertiesTest.java | 51 + .../configuration/QueueConfigurationTest.java | 291 +++ .../VirtualHostConfigurationTest.java | 408 ++++ .../plugins/AbstractConfigurationTest.java | 210 ++ .../AuthenticationProviderRecovererTest.java | 143 ++ .../configuration/startup/BrokerRecovererTest.java | 394 ++++ .../startup/DefaultRecovererProviderTest.java | 69 + .../startup/GroupProviderRecovererTest.java | 101 + .../startup/KeyStoreRecovererTest.java | 118 + .../configuration/startup/PluginRecovererTest.java | 117 + .../startup/PreferencesProviderRecovererTest.java | 96 + .../startup/TrustStoreRecovererTest.java | 117 + .../startup/VirtualHostRecovererTest.java | 132 ++ .../store/ConfigurationEntryStoreTestCase.java | 391 ++++ .../store/JsonConfigurationEntryStoreTest.java | 236 ++ .../store/ManagementModeStoreHandlerTest.java | 335 +++ .../store/MemoryConfigurationEntryStoreTest.java | 127 ++ .../StoreConfigurationChangeListenerTest.java | 103 + .../configuration/updater/TaskExecutorTest.java | 296 +++ .../exchange/DefaultExchangeFactoryTest.java | 226 ++ .../qpid/server/exchange/FanoutExchangeTest.java | 194 ++ .../qpid/server/exchange/HeadersBindingTest.java | 334 +++ .../qpid/server/exchange/HeadersExchangeTest.java | 252 +++ .../qpid/server/exchange/TopicExchangeTest.java | 361 +++ .../qpid/server/filter/JMSSelectorFilterTest.java | 56 + .../server/logging/Log4jMessageLoggerTest.java | 271 +++ .../apache/qpid/server/logging/LogMessageTest.java | 170 ++ .../qpid/server/logging/UnitTestMessageLogger.java | 76 + .../server/logging/UnitTestMessageLoggerTest.java | 103 + .../logging/actors/AMQPChannelActorTest.java | 106 + .../logging/actors/AMQPConnectionActorTest.java | 122 + .../actors/AbstractManagementActorTest.java | 86 + .../server/logging/actors/BaseActorTestCase.java | 120 + .../actors/BaseConnectionActorTestCase.java | 74 + .../server/logging/actors/CurrentActorTest.java | 253 +++ .../logging/actors/HttpManagementActorTest.java | 94 + .../server/logging/actors/ManagementActorTest.java | 186 ++ .../qpid/server/logging/actors/QueueActorTest.java | 75 + .../logging/actors/SubscriptionActorTest.java | 86 + .../qpid/server/logging/actors/TestLogActor.java | 37 + .../logging/log4j/LoggingFacadeTest.log4j.xml | 41 + .../logging/log4j/LoggingManagementFacadeTest.java | 243 ++ .../logging/messages/AbstractTestMessages.java | 128 ++ .../logging/messages/BindingMessagesTest.java | 64 + .../logging/messages/BrokerMessagesTest.java | 146 ++ .../logging/messages/ChannelMessagesTest.java | 73 + .../logging/messages/ConnectionMessagesTest.java | 114 + .../logging/messages/ExchangeMessagesTest.java | 87 + .../messages/ManagementConsoleMessagesTest.java | 98 + .../logging/messages/MessageStoreMessagesTest.java | 71 + .../server/logging/messages/QueueMessagesTest.java | 239 ++ .../logging/messages/SubscriptionMessagesTest.java | 86 + .../logging/messages/VirtualHostMessagesTest.java | 51 + .../logging/subjects/AbstractTestLogSubject.java | 285 +++ .../logging/subjects/BindingLogSubjectTest.java | 80 + .../logging/subjects/ChannelLogSubjectTest.java | 62 + .../logging/subjects/ConnectionLogSubjectTest.java | 69 + .../logging/subjects/ExchangeLogSubjectTest.java | 68 + .../subjects/MessageStoreLogSubjectTest.java | 73 + .../logging/subjects/QueueLogSubjectTest.java | 72 + .../subjects/SubscriptionLogSubjectTest.java | 105 + .../server/logging/subjects/TestBlankSubject.java | 33 + .../model/ConfiguredObjectStateTransitionTest.java | 266 +++ .../qpid/server/model/UUIDGeneratorTest.java | 217 ++ .../apache/qpid/server/model/VirtualHostTest.java | 147 ++ .../adapter/AuthenticationProviderFactoryTest.java | 156 ++ .../FileSystemPreferencesProviderFactoryTest.java | 139 ++ .../adapter/FileSystemPreferencesProviderTest.java | 266 +++ .../qpid/server/model/adapter/PortFactoryTest.java | 389 ++++ .../configuration/ConfigurationEntryTest.java | 129 ++ .../qpid/server/queue/AMQPriorityQueueTest.java | 112 + .../qpid/server/queue/AMQQueueFactoryTest.java | 560 +++++ .../qpid/server/queue/ConflationQueueListTest.java | 210 ++ .../server/queue/InboundMessageAdapterTest.java | 89 + .../org/apache/qpid/server/queue/MockAMQQueue.java | 615 +++++ .../apache/qpid/server/queue/MockQueueEntry.java | 253 +++ .../qpid/server/queue/NotificationCheckTest.java | 106 + .../qpid/server/queue/PriorityQueueListTest.java | 117 + .../qpid/server/queue/QueueEntryImplTestBase.java | 253 +++ .../qpid/server/queue/QueueEntryListTestBase.java | 241 ++ .../queue/SelfValidatingSortedQueueEntryList.java | 161 ++ .../qpid/server/queue/SimpleAMQQueueTest.java | 1248 +++++++++++ .../server/queue/SimpleAMQQueueThreadPoolTest.java | 69 + .../server/queue/SimpleQueueEntryImplTest.java | 83 + .../server/queue/SimpleQueueEntryListTest.java | 247 ++ .../server/queue/SortedQueueEntryImplTest.java | 93 + .../server/queue/SortedQueueEntryListTest.java | 373 +++ .../qpid/server/security/SubjectCreatorTest.java | 169 ++ .../security/auth/AuthenticatedPrincipalTest.java | 147 ++ .../auth/AuthenticatedPrincipalTestHelper.java | 54 + .../security/auth/AuthenticationResultTest.java | 112 + .../server/security/auth/TestPrincipalUtils.java | 49 + .../security/auth/UsernamePrincipalTest.java | 70 + ...Base64MD5PasswordFilePrincipalDatabaseTest.java | 465 ++++ .../security/auth/database/HashedUserTest.java | 83 + .../PlainPasswordFilePrincipalDatabaseTest.java | 415 ++++ .../security/auth/database/PlainUserTest.java | 78 + .../auth/jmx/JMXPasswordAuthenticatorTest.java | 259 +++ .../AnonymousAuthenticationManagerTest.java | 78 + ...sswordFileAuthenticationManagerFactoryTest.java | 111 + .../manager/ExternalAuthenticationManagerTest.java | 183 ++ ...sswordFileAuthenticationManagerFactoryTest.java | 107 + ...PrincipalDatabaseAuthenticationManagerTest.java | 348 +++ .../manager/SimpleAuthenticationManagerTest.java | 160 ++ ...SimpleLDAPAuthenticationManagerFactoryTest.java | 48 + .../auth/sasl/CRAMMD5HexInitialiserTest.java | 145 ++ .../security/auth/sasl/CRAMMD5HexServerTest.java | 227 ++ .../security/auth/sasl/SaslServerTestCase.java | 66 + .../qpid/server/security/auth/sasl/SaslUtil.java | 85 + .../security/auth/sasl/TestPrincipalDatabase.java | 107 + .../auth/sasl/amqplain/AMQPlainSaslServerTest.java | 43 + .../auth/sasl/plain/PlainSaslServerTest.java | 39 + .../security/group/FileGroupDatabaseTest.java | 456 ++++ .../group/FileGroupManagerFactoryTest.java | 77 + .../security/group/FileGroupManagerTest.java | 204 ++ .../server/security/group/GroupPrincipalTest.java | 88 + .../qpid/server/stats/StatisticsCounterTest.java | 149 ++ .../AbstractDurableConfigurationStoreTestCase.java | 520 +++++ .../apache/qpid/server/store/EventManagerTest.java | 72 + .../qpid/server/store/JsonFileConfigStoreTest.java | 299 +++ .../store/MessageStoreQuotaEventsTestBase.java | 191 ++ .../qpid/server/store/MessageStoreTestCase.java | 78 + .../store/OperationalLoggingListenerTest.java | 187 ++ .../apache/qpid/server/store/StateManagerTest.java | 199 ++ .../qpid/server/store/TestMemoryMessageStore.java | 34 + .../store/TestMemoryMessageStoreFactory.java | 54 + .../server/store/TestableMemoryMessageStore.java | 149 ++ .../qpid/server/subscription/MockSubscription.java | 579 +++++ .../server/subscription/SubscriptionListTest.java | 429 ++++ .../server/txn/AsyncAutoCommitTransactionTest.java | 140 ++ .../qpid/server/txn/AutoCommitTransactionTest.java | 442 ++++ .../qpid/server/txn/LocalTransactionTest.java | 672 ++++++ .../org/apache/qpid/server/txn/MockAction.java | 54 + .../apache/qpid/server/txn/MockServerMessage.java | 110 + .../qpid/server/txn/MockStoreTransaction.java | 135 ++ .../apache/qpid/server/util/BrokerTestHelper.java | 190 ++ .../qpid/server/util/MapJsonSerializerTest.java | 53 + .../apache/qpid/server/util/StringUtilTest.java | 58 + .../DurableConfigurationRecovererTest.java | 479 ++++ .../server/virtualhost/HouseKeepingTaskTest.java | 113 + .../qpid/server/virtualhost/MockVirtualHost.java | 304 +++ .../virtualhost/StandardVirtualHostTest.java | 376 ++++ .../org/apache/qpid/tools/security/PasswdTest.java | 38 + ...g.apache.qpid.server.plugin.MessageStoreFactory | 19 + .../qpid/server/logging/GenerateLogMessages.java | 532 +++++ .../qpid/server/logging/messages/LogMessages.vm | 189 ++ qpid/java/broker-core/src/xsl/qmf.xsl | 908 ++++++++ qpid/java/broker/bin/qpid-passwd | 35 - qpid/java/broker/bin/qpid-server | 56 - qpid/java/broker/bin/qpid-server.bat | 201 -- qpid/java/broker/bin/qpid.stop | 178 -- qpid/java/broker/bin/qpid.stopall | 27 - qpid/java/broker/build-generate-sources.xml | 97 - qpid/java/broker/build.xml | 87 - qpid/java/broker/etc/broker_example.acl | 131 -- qpid/java/broker/etc/groups | 29 - qpid/java/broker/etc/log4j.xml | 123 - qpid/java/broker/etc/md5passwd | 23 - qpid/java/broker/etc/passwd | 25 - qpid/java/broker/python-test.xml | 56 - qpid/java/broker/scripts/resetAlerting.sh | 116 - qpid/java/broker/src/main/java/broker.bnd | 26 - .../broker/src/main/java/fallback-log4j.properties | 24 - .../apache/log4j/QpidCompositeRollingAppender.java | 1206 ---------- .../main/java/org/apache/qpid/server/Broker.java | 279 --- .../java/org/apache/qpid/server/BrokerOptions.java | 376 ---- .../src/main/java/org/apache/qpid/server/Main.java | 381 ---- .../qpid/server/TransactionTimeoutHelper.java | 102 - .../org/apache/qpid/server/binding/Binding.java | 127 -- .../BrokerConfigurationStoreCreator.java | 82 - .../server/configuration/BrokerProperties.java | 81 - .../server/configuration/ConfigurationEntry.java | 203 -- .../configuration/ConfigurationEntryStore.java | 87 - .../configuration/ConfiguredObjectRecoverer.java | 28 - .../configuration/ExchangeConfiguration.java | 58 - .../IllegalConfigurationException.java | 37 - .../server/configuration/QueueConfiguration.java | 211 -- .../server/configuration/RecovererProvider.java | 28 - .../configuration/VirtualHostConfiguration.java | 301 --- .../configuration/XmlConfigurationUtilities.java | 93 - .../plugins/AbstractConfiguration.java | 344 --- .../startup/AccessControlProviderRecoverer.java | 54 - .../startup/AuthenticationProviderRecoverer.java | 95 - .../configuration/startup/BrokerRecoverer.java | 207 -- .../startup/DefaultRecovererProvider.java | 136 -- .../startup/GroupProviderRecoverer.java | 53 - .../configuration/startup/KeyStoreRecoverer.java | 40 - .../configuration/startup/PluginRecoverer.java | 66 - .../configuration/startup/PortRecoverer.java | 52 - .../startup/PreferencesProviderRecoverer.java | 41 - .../configuration/startup/RecovererHelper.java | 44 - .../configuration/startup/StoreUpgrader.java | 86 - .../configuration/startup/TrustStoreRecoverer.java | 40 - .../startup/VirtualHostRecoverer.java | 51 - .../store/ConfigurationEntryStoreUtil.java | 91 - .../store/JsonConfigurationEntryStore.java | 128 -- .../store/ManagementModeStoreHandler.java | 353 --- .../store/MemoryConfigurationEntryStore.java | 709 ------ .../store/StoreConfigurationChangeListener.java | 219 -- .../factory/JsonConfigurationStoreFactory.java | 42 - .../factory/MemoryConfigurationStoreFactory.java | 42 - .../updater/ChangeAttributesTask.java | 62 - .../configuration/updater/ChangeStateTask.java | 67 - .../configuration/updater/CreateChildTask.java | 78 - .../configuration/updater/SetAttributeTask.java | 74 - .../server/configuration/updater/TaskExecutor.java | 324 --- .../qpid/server/connection/ConnectionRegistry.java | 131 -- .../server/connection/IConnectionRegistry.java | 54 - .../qpid/server/exchange/AbstractExchange.java | 679 ------ .../qpid/server/exchange/DefaultExchange.java | 346 --- .../server/exchange/DefaultExchangeFactory.java | 145 -- .../server/exchange/DefaultExchangeRegistry.java | 219 -- .../qpid/server/exchange/DirectExchange.java | 215 -- .../qpid/server/exchange/DirectExchangeType.java | 53 - .../org/apache/qpid/server/exchange/Exchange.java | 168 -- .../qpid/server/exchange/ExchangeFactory.java | 43 - .../server/exchange/ExchangeInUseException.java | 45 - .../qpid/server/exchange/ExchangeInitialiser.java | 53 - .../qpid/server/exchange/ExchangeReferrer.java | 26 - .../qpid/server/exchange/ExchangeRegistry.java | 69 - .../qpid/server/exchange/FanoutExchange.java | 224 -- .../qpid/server/exchange/FanoutExchangeType.java | 52 - .../apache/qpid/server/exchange/FilterSupport.java | 218 -- .../qpid/server/exchange/HeadersBinding.java | 284 --- .../qpid/server/exchange/HeadersExchange.java | 166 -- .../qpid/server/exchange/HeadersExchangeType.java | 53 - .../apache/qpid/server/exchange/TopicExchange.java | 262 --- .../qpid/server/exchange/TopicExchangeType.java | 53 - .../server/exchange/topic/TopicExchangeResult.java | 209 -- .../exchange/topic/TopicMatcherDFAState.java | 310 --- .../server/exchange/topic/TopicMatcherResult.java | 25 - .../server/exchange/topic/TopicNormalizer.java | 92 - .../qpid/server/exchange/topic/TopicParser.java | 456 ---- .../qpid/server/exchange/topic/TopicWord.java | 39 - .../server/exchange/topic/TopicWordDictionary.java | 56 - .../apache/qpid/server/filter/FilterManager.java | 37 - .../qpid/server/filter/FilterManagerFactory.java | 93 - .../qpid/server/filter/JMSSelectorFilter.java | 155 -- .../apache/qpid/server/filter/MessageFilter.java | 28 - .../qpid/server/filter/NoConsumerFilter.java | 49 - .../qpid/server/filter/SimpleFilterManager.java | 100 - .../server/flow/AbstractFlowCreditManager.java | 74 - .../qpid/server/flow/BytesOnlyCreditManager.java | 87 - .../apache/qpid/server/flow/FlowCreditManager.java | 46 - .../qpid/server/flow/LimitlessCreditManager.java | 53 - .../server/flow/MessageAndBytesCreditManager.java | 90 - .../qpid/server/flow/MessageOnlyCreditManager.java | 86 - .../qpid/server/flow/Pre0_10CreditManager.java | 192 -- .../server/logging/AbstractRootMessageLogger.java | 58 - .../logging/CompositeStartupMessageLogger.java | 51 - .../qpid/server/logging/Log4jMessageLogger.java | 69 - .../org/apache/qpid/server/logging/LogActor.java | 66 - .../org/apache/qpid/server/logging/LogMessage.java | 26 - .../apache/qpid/server/logging/LogRecorder.java | 243 -- .../org/apache/qpid/server/logging/LogSubject.java | 36 - .../qpid/server/logging/NullRootMessageLogger.java | 47 - .../qpid/server/logging/RootMessageLogger.java | 75 - .../server/logging/SystemOutMessageLogger.java | 51 - .../server/logging/actors/AMQPChannelActor.java | 61 - .../server/logging/actors/AMQPConnectionActor.java | 53 - .../qpid/server/logging/actors/AbstractActor.java | 71 - .../logging/actors/AbstractManagementActor.java | 68 - .../qpid/server/logging/actors/BrokerActor.java | 53 - .../qpid/server/logging/actors/CurrentActor.java | 146 -- .../qpid/server/logging/actors/GenericActor.java | 84 - .../server/logging/actors/HttpManagementActor.java | 62 - .../server/logging/actors/ManagementActor.java | 99 - .../qpid/server/logging/actors/QueueActor.java | 53 - .../server/logging/actors/SubscriptionActor.java | 46 - .../logging/log4j/LoggingFacadeException.java | 45 - .../logging/log4j/LoggingManagementFacade.java | 579 ----- .../messages/Binding_logmessages.properties | 22 - .../logging/messages/Broker_logmessages.properties | 50 - .../messages/Channel_logmessages.properties | 40 - .../messages/ConfigStore_logmessages.properties | 26 - .../messages/Connection_logmessages.properties | 26 - .../messages/Exchange_logmessages.properties | 25 - .../ManagementConsole_logmessages.properties | 38 - .../messages/MessageStore_logmessages.properties | 30 - .../logging/messages/Queue_logmessages.properties | 26 - .../messages/Subscription_logmessages.properties | 24 - .../messages/TransactionLog_logmessages.properties | 38 - .../messages/VirtualHost_logmessages.properties | 28 - .../logging/subjects/AbstractLogSubject.java | 72 - .../server/logging/subjects/BindingLogSubject.java | 51 - .../server/logging/subjects/ChannelLogSubject.java | 56 - .../logging/subjects/ConnectionLogSubject.java | 106 - .../logging/subjects/ExchangeLogSubject.java | 37 - .../server/logging/subjects/LogSubjectFormat.java | 118 - .../logging/subjects/MessageStoreLogSubject.java | 37 - .../server/logging/subjects/QueueLogSubject.java | 37 - .../logging/subjects/SubscriptionLogSubject.java | 55 - .../qpid/server/message/AMQMessageHeader.java | 61 - .../server/message/AbstractServerMessageImpl.java | 111 - .../qpid/server/message/EnqueableMessage.java | 30 - .../apache/qpid/server/message/InboundMessage.java | 37 - .../qpid/server/message/MessageContentSource.java | 32 - .../qpid/server/message/MessageReference.java | 58 - .../apache/qpid/server/message/ServerMessage.java | 54 - .../qpid/server/model/AccessControlProvider.java | 56 - .../org/apache/qpid/server/model/Attribute.java | 199 -- .../qpid/server/model/AuthenticationMethod.java | 33 - .../qpid/server/model/AuthenticationProvider.java | 80 - .../java/org/apache/qpid/server/model/Binding.java | 75 - .../java/org/apache/qpid/server/model/Broker.java | 190 -- .../server/model/ConfigurationChangeListener.java | 39 - .../apache/qpid/server/model/ConfiguredObject.java | 262 --- .../qpid/server/model/ConfiguredObjectFinder.java | 38 - .../org/apache/qpid/server/model/Connection.java | 112 - .../org/apache/qpid/server/model/Consumer.java | 73 - .../java/org/apache/qpid/server/model/Event.java | 27 - .../org/apache/qpid/server/model/EventType.java | 60 - .../org/apache/qpid/server/model/Exchange.java | 91 - .../java/org/apache/qpid/server/model/Group.java | 52 - .../org/apache/qpid/server/model/GroupMember.java | 52 - .../apache/qpid/server/model/GroupProvider.java | 55 - .../model/IllegalStateTransitionException.java | 43 - .../server/model/IntegrityViolationException.java | 37 - .../org/apache/qpid/server/model/KeyStore.java | 72 - .../apache/qpid/server/model/LifetimePolicy.java | 27 - .../java/org/apache/qpid/server/model/Model.java | 124 - ...rdCredentialManagingAuthenticationProvider.java | 46 - .../java/org/apache/qpid/server/model/Plugin.java | 52 - .../java/org/apache/qpid/server/model/Port.java | 111 - .../qpid/server/model/PreferencesProvider.java | 89 - .../org/apache/qpid/server/model/Protocol.java | 113 - .../org/apache/qpid/server/model/Publisher.java | 25 - .../java/org/apache/qpid/server/model/Queue.java | 155 -- .../server/model/QueueNotificationListener.java | 28 - .../org/apache/qpid/server/model/QueueType.java | 29 - .../java/org/apache/qpid/server/model/Session.java | 82 - .../java/org/apache/qpid/server/model/State.java | 32 - .../org/apache/qpid/server/model/Statistics.java | 26 - .../org/apache/qpid/server/model/Transport.java | 51 - .../org/apache/qpid/server/model/TrustStore.java | 73 - .../apache/qpid/server/model/UUIDGenerator.java | 100 - .../java/org/apache/qpid/server/model/User.java | 65 - .../org/apache/qpid/server/model/VirtualHost.java | 171 -- .../apache/qpid/server/model/VirtualHostAlias.java | 37 - .../qpid/server/model/adapter/AbstractAdapter.java | 477 ---- .../model/adapter/AbstractKeyStoreAdapter.java | 188 -- .../model/adapter/AbstractPluginAdapter.java | 191 -- .../adapter/AccessControlProviderAdapter.java | 300 --- .../adapter/AccessControlProviderFactory.java | 90 - .../qpid/server/model/adapter/AmqpPortAdapter.java | 277 --- .../adapter/AuthenticationProviderAdapter.java | 826 ------- .../adapter/AuthenticationProviderFactory.java | 111 - .../qpid/server/model/adapter/BindingAdapter.java | 240 -- .../qpid/server/model/adapter/BrokerAdapter.java | 1267 ----------- .../server/model/adapter/ConnectionAdapter.java | 322 --- .../qpid/server/model/adapter/ConsumerAdapter.java | 242 -- .../qpid/server/model/adapter/ExchangeAdapter.java | 444 ---- .../adapter/FileSystemPreferencesProvider.java | 592 ----- .../FileSystemPreferencesProviderFactory.java | 53 - .../server/model/adapter/GroupProviderAdapter.java | 710 ------ .../server/model/adapter/GroupProviderFactory.java | 118 - .../qpid/server/model/adapter/KeyStoreAdapter.java | 254 --- .../qpid/server/model/adapter/NoStatistics.java | 46 - .../qpid/server/model/adapter/PortAdapter.java | 559 ----- .../qpid/server/model/adapter/PortFactory.java | 192 -- .../model/adapter/PreferencesProviderCreator.java | 66 - .../qpid/server/model/adapter/QueueAdapter.java | 816 ------- .../qpid/server/model/adapter/SessionAdapter.java | 293 --- .../server/model/adapter/StatisticsAdapter.java | 67 - .../server/model/adapter/TrustStoreAdapter.java | 255 --- .../server/model/adapter/VirtualHostAdapter.java | 1273 ----------- .../model/adapter/VirtualHostAliasAdapter.java | 150 -- .../qpid/server/plugin/AccessControlFactory.java | 51 - .../plugin/AuthenticationManagerFactory.java | 58 - .../server/plugin/ConfigurationStoreFactory.java | 48 - .../plugin/DurableConfigurationStoreFactory.java | 35 - .../apache/qpid/server/plugin/ExchangeType.java | 38 - .../qpid/server/plugin/GroupManagerFactory.java | 51 - .../plugin/JDBCConnectionProviderFactory.java | 79 - .../qpid/server/plugin/MessageConverter.java | 32 - .../qpid/server/plugin/MessageMetaDataType.java | 42 - .../qpid/server/plugin/MessageStoreFactory.java | 36 - .../org/apache/qpid/server/plugin/Pluggable.java | 25 - .../apache/qpid/server/plugin/PluginFactory.java | 32 - .../server/plugin/PreferencesProviderFactory.java | 12 - .../qpid/server/plugin/ProtocolEngineCreator.java | 35 - .../qpid/server/plugin/QpidServiceLoader.java | 72 - .../qpid/server/plugin/VirtualHostFactory.java | 92 - .../qpid/server/protocol/AMQConnectionModel.java | 97 - .../qpid/server/protocol/AMQSessionModel.java | 91 - .../qpid/server/protocol/AmqpProtocolVersion.java | 23 - .../org/apache/qpid/server/protocol/LinkModel.java | 25 - .../apache/qpid/server/protocol/LinkRegistry.java | 79 - .../server/protocol/MessageConverterRegistry.java | 59 - .../protocol/MultiVersionProtocolEngine.java | 680 ------ .../MultiVersionProtocolEngineFactory.java | 89 - .../apache/qpid/server/queue/AMQPriorityQueue.java | 46 - .../org/apache/qpid/server/queue/AMQQueue.java | 331 --- .../apache/qpid/server/queue/AMQQueueFactory.java | 542 ----- .../org/apache/qpid/server/queue/BaseQueue.java | 44 - .../apache/qpid/server/queue/ConflationQueue.java | 48 - .../qpid/server/queue/ConflationQueueList.java | 256 --- .../qpid/server/queue/DefaultQueueRegistry.java | 129 -- .../org/apache/qpid/server/queue/Filterable.java | 32 - .../qpid/server/queue/InboundMessageAdapter.java | 71 - .../qpid/server/queue/NotificationCheck.java | 150 -- .../apache/qpid/server/queue/OutOfOrderQueue.java | 70 - .../qpid/server/queue/PriorityQueueList.java | 217 -- .../qpid/server/queue/QueueArgumentsConverter.java | 152 -- .../org/apache/qpid/server/queue/QueueContext.java | 64 - .../org/apache/qpid/server/queue/QueueEntry.java | 253 --- .../apache/qpid/server/queue/QueueEntryImpl.java | 517 ----- .../qpid/server/queue/QueueEntryIterator.java | 30 - .../apache/qpid/server/queue/QueueEntryList.java | 40 - .../qpid/server/queue/QueueEntryListFactory.java | 26 - .../qpid/server/queue/QueueEntryVisitor.java | 22 - .../org/apache/qpid/server/queue/QueueFactory.java | 47 - .../apache/qpid/server/queue/QueueRegistry.java | 51 - .../org/apache/qpid/server/queue/QueueRunner.java | 127 -- .../apache/qpid/server/queue/SimpleAMQQueue.java | 2289 ------------------- .../qpid/server/queue/SimpleQueueEntryImpl.java | 78 - .../qpid/server/queue/SimpleQueueEntryList.java | 205 -- .../org/apache/qpid/server/queue/SortedQueue.java | 58 - .../qpid/server/queue/SortedQueueEntryImpl.java | 143 -- .../qpid/server/queue/SortedQueueEntryList.java | 659 ------ .../server/queue/SortedQueueEntryListFactory.java | 38 - .../apache/qpid/server/queue/SubFlushRunner.java | 119 - .../qpid/server/registry/ApplicationRegistry.java | 355 --- .../qpid/server/registry/IApplicationRegistry.java | 38 - .../apache/qpid/server/security/AccessControl.java | 66 - .../qpid/server/security/AuthorizationHolder.java | 49 - .../org/apache/qpid/server/security/Result.java | 46 - .../qpid/server/security/SecurityManager.java | 643 ------ .../qpid/server/security/SubjectCreator.java | 162 -- .../access/FileAccessControlProviderConstants.java | 27 - .../server/security/access/ObjectProperties.java | 336 --- .../qpid/server/security/access/ObjectType.java | 107 - .../qpid/server/security/access/Operation.java | 56 - .../security/access/OperationLoggingDetails.java | 53 - .../qpid/server/security/access/Permission.java | 47 - .../security/auth/AuthenticatedPrincipal.java | 127 -- .../server/security/auth/AuthenticationResult.java | 145 -- .../security/auth/SubjectAuthenticationResult.java | 76 - .../server/security/auth/UsernamePrincipal.java | 77 - .../AbstractPasswordFilePrincipalDatabase.java | 465 ---- .../Base64MD5PasswordFilePrincipalDatabase.java | 156 -- .../server/security/auth/database/HashedUser.java | 194 -- .../security/auth/database/PasswordPrincipal.java | 40 - .../PlainPasswordFilePrincipalDatabase.java | 146 -- .../server/security/auth/database/PlainUser.java | 109 - .../security/auth/database/PrincipalDatabase.java | 114 - .../auth/jmx/JMXPasswordAuthenticator.java | 136 -- ...bstractPrincipalDatabaseAuthManagerFactory.java | 74 - .../manager/AnonymousAuthenticationManager.java | 125 -- .../AnonymousAuthenticationManagerFactory.java | 59 - .../auth/manager/AuthenticationManager.java | 94 - ...D5PasswordFileAuthenticationManagerFactory.java | 51 - .../manager/ExternalAuthenticationManager.java | 114 - .../ExternalAuthenticationManagerFactory.java | 70 - ...icationProviderAttributeDescriptions.properties | 19 - .../manager/KerberosAuthenticationManager.java | 143 -- .../KerberosAuthenticationManagerFactory.java | 59 - ...icationProviderAttributeDescriptions.properties | 19 - ...inPasswordFileAuthenticationManagerFactory.java | 50 - .../PrincipalDatabaseAuthenticationManager.java | 163 -- .../auth/manager/SimpleAuthenticationManager.java | 214 -- .../manager/SimpleLDAPAuthenticationManager.java | 317 --- .../SimpleLDAPAuthenticationManagerFactory.java | 94 - ...icationProviderAttributeDescriptions.properties | 23 - .../sasl/AuthenticationProviderInitialiser.java | 39 - .../auth/sasl/UsernamePasswordInitialiser.java | 103 - .../auth/sasl/amqplain/AmqPlainInitialiser.java | 31 - .../auth/sasl/amqplain/AmqPlainSaslServer.java | 132 -- .../sasl/amqplain/AmqPlainSaslServerFactory.java | 60 - .../auth/sasl/anonymous/AnonymousSaslServer.java | 78 - .../sasl/anonymous/AnonymousSaslServerFactory.java | 61 - .../sasl/crammd5/CRAMMD5HashedInitialiser.java | 37 - .../auth/sasl/crammd5/CRAMMD5HashedSaslServer.java | 105 - .../sasl/crammd5/CRAMMD5HashedServerFactory.java | 60 - .../auth/sasl/crammd5/CRAMMD5HexInitialiser.java | 149 -- .../auth/sasl/crammd5/CRAMMD5HexSaslServer.java | 105 - .../auth/sasl/crammd5/CRAMMD5HexServerFactory.java | 60 - .../auth/sasl/crammd5/CRAMMD5Initialiser.java | 33 - .../auth/sasl/external/ExternalSaslServer.java | 125 -- .../security/auth/sasl/plain/PlainInitialiser.java | 31 - .../auth/sasl/plain/PlainPasswordCallback.java | 80 - .../security/auth/sasl/plain/PlainSaslServer.java | 161 -- .../auth/sasl/plain/PlainSaslServerFactory.java | 60 - .../server/security/group/FileGroupDatabase.java | 287 --- .../server/security/group/FileGroupManager.java | 239 -- .../security/group/FileGroupManagerFactory.java | 79 - ...leGroupProviderAttributeDescriptions.properties | 19 - .../qpid/server/security/group/GroupDatabase.java | 34 - .../qpid/server/security/group/GroupManager.java | 48 - .../qpid/server/security/group/GroupPrincipal.java | 100 - .../qpid/server/stats/StatisticsCounter.java | 157 -- .../qpid/server/stats/StatisticsGatherer.java | 106 - .../AbstractDurableConfiguredObjectRecoverer.java | 77 - .../server/store/AbstractJDBCMessageStore.java | 2363 -------------------- .../server/store/AbstractMemoryMessageStore.java | 147 -- .../server/store/ConfigurationRecoveryHandler.java | 41 - .../qpid/server/store/ConfiguredObjectRecord.java | 88 - .../qpid/server/store/DependencyListener.java | 28 - .../store/DurableConfigurationRecoverer.java | 242 -- .../server/store/DurableConfigurationStore.java | 99 - .../store/DurableConfigurationStoreCreator.java | 78 - .../store/DurableConfigurationStoreHelper.java | 145 -- .../store/DurableConfigurationStoreUpgrader.java | 35 - .../store/DurableConfiguredObjectRecoverer.java | 33 - .../java/org/apache/qpid/server/store/Event.java | 42 - .../apache/qpid/server/store/EventListener.java | 25 - .../org/apache/qpid/server/store/EventManager.java | 63 - .../apache/qpid/server/store/HAMessageStore.java | 29 - .../qpid/server/store/JsonFileConfigStore.java | 513 ----- .../server/store/JsonFileConfigStoreFactory.java | 52 - .../server/store/MessageMetaDataTypeRegistry.java | 65 - .../org/apache/qpid/server/store/MessageStore.java | 74 - .../server/store/MessageStoreClosedException.java | 38 - .../qpid/server/store/MessageStoreConstants.java | 31 - .../qpid/server/store/MessageStoreCreator.java | 79 - .../server/store/MessageStoreRecoveryHandler.java | 33 - .../apache/qpid/server/store/NonNullUpgrader.java | 62 - .../apache/qpid/server/store/NullMessageStore.java | 115 - .../org/apache/qpid/server/store/NullUpgrader.java | 58 - .../server/store/OperationalLoggingListener.java | 86 - .../java/org/apache/qpid/server/store/State.java | 47 - .../org/apache/qpid/server/store/StateManager.java | 150 -- .../qpid/server/store/StorableMessageMetaData.java | 38 - .../org/apache/qpid/server/store/StoreContext.java | 73 - .../org/apache/qpid/server/store/StoreFuture.java | 40 - .../qpid/server/store/StoredMemoryMessage.java | 139 -- .../apache/qpid/server/store/StoredMessage.java | 40 - .../org/apache/qpid/server/store/Transaction.java | 81 - .../apache/qpid/server/store/TransactionLog.java | 26 - .../store/TransactionLogRecoveryHandler.java | 42 - .../qpid/server/store/TransactionLogResource.java | 28 - .../qpid/server/store/UnresolvedDependency.java | 31 - .../apache/qpid/server/store/UnresolvedObject.java | 28 - .../apache/qpid/server/store/UpgraderProvider.java | 26 - .../qpid/server/store/jdbc/ConnectionProvider.java | 32 - .../AssignedSubscriptionMessageGroupManager.java | 158 -- .../server/subscription/ClientDeliveryMethod.java | 29 - .../DefinedGroupMessageGroupManager.java | 270 --- .../server/subscription/MessageGroupManager.java | 41 - .../server/subscription/RecordDeliveryMethod.java | 28 - .../qpid/server/subscription/Subscription.java | 124 - .../qpid/server/subscription/SubscriptionList.java | 280 --- .../qpid/server/txn/AlreadyKnownDtxException.java | 32 - .../server/txn/AsyncAutoCommitTransaction.java | 360 --- .../qpid/server/txn/AutoCommitTransaction.java | 282 --- .../qpid/server/txn/DistributedTransaction.java | 249 --- .../java/org/apache/qpid/server/txn/DtxBranch.java | 413 ---- .../org/apache/qpid/server/txn/DtxException.java | 44 - .../qpid/server/txn/DtxNotSelectedException.java | 30 - .../org/apache/qpid/server/txn/DtxRegistry.java | 334 --- .../server/txn/IncorrectDtxStateException.java | 32 - .../qpid/server/txn/JoinAndResumeDtxException.java | 32 - .../apache/qpid/server/txn/LocalTransaction.java | 483 ---- .../qpid/server/txn/NotAssociatedDtxException.java | 32 - .../qpid/server/txn/RollbackOnlyDtxException.java | 32 - .../apache/qpid/server/txn/ServerTransaction.java | 122 - .../server/txn/SuspendAndFailDtxException.java | 32 - .../qpid/server/txn/TimeoutDtxException.java | 32 - .../qpid/server/txn/UnknownDtxBranchException.java | 32 - .../qpid/server/util/ByteBufferOutputStream.java | 46 - .../apache/qpid/server/util/MapJsonSerializer.java | 69 - .../apache/qpid/server/util/MapValueConverter.java | 412 ---- .../qpid/server/util/ParameterizedTypeImpl.java | 73 - .../qpid/server/util/ResourceBundleLoader.java | 49 - .../org/apache/qpid/server/util/StringUtil.java | 44 - .../server/virtualhost/AbstractVirtualHost.java | 930 -------- .../qpid/server/virtualhost/BindingRecoverer.java | 177 -- .../virtualhost/DefaultUpgraderProvider.java | 266 --- .../virtualhost/ExchangeExistsException.java | 39 - .../virtualhost/ExchangeIsAlternateException.java | 30 - .../qpid/server/virtualhost/ExchangeRecoverer.java | 102 - .../qpid/server/virtualhost/HouseKeepingTask.java | 84 - .../server/virtualhost/ManagedVirtualHost.java | 44 - .../qpid/server/virtualhost/QueueRecoverer.java | 157 -- .../virtualhost/RequiredExchangeException.java | 30 - .../virtualhost/ReservedExchangeNameException.java | 38 - .../server/virtualhost/StandardVirtualHost.java | 135 -- .../virtualhost/StandardVirtualHostFactory.java | 118 - .../org/apache/qpid/server/virtualhost/State.java | 31 - .../virtualhost/UnknownExchangeException.java | 38 - .../qpid/server/virtualhost/VirtualHost.java | 126 -- .../VirtualHostConfigRecoveryHandler.java | 375 ---- .../virtualhost/VirtualHostFactoryRegistry.java | 65 - .../server/virtualhost/VirtualHostListener.java | 41 - .../server/virtualhost/VirtualHostRegistry.java | 95 - .../virtualhost/plugins/QueueExistsException.java | 40 - .../org/apache/qpid/tools/security/Passwd.java | 70 - ...qpid.server.plugin.AuthenticationManagerFactory | 24 - ...he.qpid.server.plugin.ConfigurationStoreFactory | 20 - ....server.plugin.DurableConfigurationStoreFactory | 19 - .../org.apache.qpid.server.plugin.ExchangeType | 22 - ...g.apache.qpid.server.plugin.GroupManagerFactory | 19 - ...pid.server.plugin.JDBCConnectionProviderFactory | 19 - ...e.qpid.server.plugin.PreferencesProviderFactory | 19 - ...rg.apache.qpid.server.plugin.VirtualHostFactory | 19 - .../broker/src/main/resources/initial-config.json | 68 - .../org/apache/qpid/server/BrokerOptionsTest.java | 329 --- .../test/java/org/apache/qpid/server/MainTest.java | 284 --- .../org/apache/qpid/server/SelectorParserTest.java | 140 -- .../qpid/server/TransactionTimeoutHelperTest.java | 230 -- .../BrokerConfigurationStoreCreatorTest.java | 165 -- .../server/configuration/BrokerPropertiesTest.java | 51 - .../configuration/QueueConfigurationTest.java | 291 --- .../VirtualHostConfigurationTest.java | 408 ---- .../plugins/AbstractConfigurationTest.java | 210 -- .../AuthenticationProviderRecovererTest.java | 143 -- .../configuration/startup/BrokerRecovererTest.java | 394 ---- .../startup/DefaultRecovererProviderTest.java | 69 - .../startup/GroupProviderRecovererTest.java | 101 - .../startup/KeyStoreRecovererTest.java | 118 - .../configuration/startup/PluginRecovererTest.java | 117 - .../startup/PreferencesProviderRecovererTest.java | 96 - .../startup/TrustStoreRecovererTest.java | 117 - .../startup/VirtualHostRecovererTest.java | 132 -- .../store/ConfigurationEntryStoreTestCase.java | 391 ---- .../store/JsonConfigurationEntryStoreTest.java | 236 -- .../store/ManagementModeStoreHandlerTest.java | 335 --- .../store/MemoryConfigurationEntryStoreTest.java | 127 -- .../StoreConfigurationChangeListenerTest.java | 103 - .../configuration/updater/TaskExecutorTest.java | 296 --- .../exchange/DefaultExchangeFactoryTest.java | 226 -- .../qpid/server/exchange/FanoutExchangeTest.java | 194 -- .../qpid/server/exchange/HeadersBindingTest.java | 334 --- .../qpid/server/exchange/HeadersExchangeTest.java | 252 --- .../qpid/server/exchange/TopicExchangeTest.java | 361 --- .../qpid/server/filter/JMSSelectorFilterTest.java | 56 - .../server/logging/Log4jMessageLoggerTest.java | 271 --- .../apache/qpid/server/logging/LogMessageTest.java | 170 -- .../qpid/server/logging/UnitTestMessageLogger.java | 76 - .../server/logging/UnitTestMessageLoggerTest.java | 103 - .../logging/actors/AMQPChannelActorTest.java | 106 - .../logging/actors/AMQPConnectionActorTest.java | 122 - .../actors/AbstractManagementActorTest.java | 86 - .../server/logging/actors/BaseActorTestCase.java | 120 - .../actors/BaseConnectionActorTestCase.java | 74 - .../server/logging/actors/CurrentActorTest.java | 253 --- .../logging/actors/HttpManagementActorTest.java | 94 - .../server/logging/actors/ManagementActorTest.java | 186 -- .../qpid/server/logging/actors/QueueActorTest.java | 75 - .../logging/actors/SubscriptionActorTest.java | 86 - .../qpid/server/logging/actors/TestLogActor.java | 37 - .../logging/log4j/LoggingFacadeTest.log4j.xml | 41 - .../logging/log4j/LoggingManagementFacadeTest.java | 243 -- .../logging/messages/AbstractTestMessages.java | 128 -- .../logging/messages/BindingMessagesTest.java | 64 - .../logging/messages/BrokerMessagesTest.java | 146 -- .../logging/messages/ChannelMessagesTest.java | 73 - .../logging/messages/ConnectionMessagesTest.java | 114 - .../logging/messages/ExchangeMessagesTest.java | 87 - .../messages/ManagementConsoleMessagesTest.java | 98 - .../logging/messages/MessageStoreMessagesTest.java | 71 - .../server/logging/messages/QueueMessagesTest.java | 239 -- .../logging/messages/SubscriptionMessagesTest.java | 86 - .../logging/messages/VirtualHostMessagesTest.java | 51 - .../logging/subjects/AbstractTestLogSubject.java | 285 --- .../logging/subjects/BindingLogSubjectTest.java | 80 - .../logging/subjects/ChannelLogSubjectTest.java | 62 - .../logging/subjects/ConnectionLogSubjectTest.java | 69 - .../logging/subjects/ExchangeLogSubjectTest.java | 68 - .../subjects/MessageStoreLogSubjectTest.java | 73 - .../logging/subjects/QueueLogSubjectTest.java | 72 - .../subjects/SubscriptionLogSubjectTest.java | 105 - .../server/logging/subjects/TestBlankSubject.java | 33 - .../model/ConfiguredObjectStateTransitionTest.java | 266 --- .../qpid/server/model/UUIDGeneratorTest.java | 217 -- .../apache/qpid/server/model/VirtualHostTest.java | 147 -- .../adapter/AuthenticationProviderFactoryTest.java | 156 -- .../FileSystemPreferencesProviderFactoryTest.java | 139 -- .../adapter/FileSystemPreferencesProviderTest.java | 266 --- .../qpid/server/model/adapter/PortFactoryTest.java | 389 ---- .../configuration/ConfigurationEntryTest.java | 129 -- .../qpid/server/queue/AMQPriorityQueueTest.java | 112 - .../qpid/server/queue/AMQQueueFactoryTest.java | 560 ----- .../qpid/server/queue/ConflationQueueListTest.java | 210 -- .../server/queue/InboundMessageAdapterTest.java | 89 - .../org/apache/qpid/server/queue/MockAMQQueue.java | 615 ----- .../apache/qpid/server/queue/MockQueueEntry.java | 253 --- .../qpid/server/queue/NotificationCheckTest.java | 106 - .../qpid/server/queue/PriorityQueueListTest.java | 117 - .../qpid/server/queue/QueueEntryImplTestBase.java | 253 --- .../qpid/server/queue/QueueEntryListTestBase.java | 241 -- .../queue/SelfValidatingSortedQueueEntryList.java | 161 -- .../qpid/server/queue/SimpleAMQQueueTest.java | 1248 ----------- .../server/queue/SimpleAMQQueueThreadPoolTest.java | 69 - .../server/queue/SimpleQueueEntryImplTest.java | 83 - .../server/queue/SimpleQueueEntryListTest.java | 247 -- .../server/queue/SortedQueueEntryImplTest.java | 93 - .../server/queue/SortedQueueEntryListTest.java | 373 --- .../qpid/server/security/SubjectCreatorTest.java | 169 -- .../security/auth/AuthenticatedPrincipalTest.java | 147 -- .../auth/AuthenticatedPrincipalTestHelper.java | 54 - .../security/auth/AuthenticationResultTest.java | 112 - .../server/security/auth/TestPrincipalUtils.java | 49 - .../security/auth/UsernamePrincipalTest.java | 70 - ...Base64MD5PasswordFilePrincipalDatabaseTest.java | 465 ---- .../security/auth/database/HashedUserTest.java | 83 - .../PlainPasswordFilePrincipalDatabaseTest.java | 415 ---- .../security/auth/database/PlainUserTest.java | 78 - .../auth/jmx/JMXPasswordAuthenticatorTest.java | 259 --- .../AnonymousAuthenticationManagerTest.java | 78 - ...sswordFileAuthenticationManagerFactoryTest.java | 111 - .../manager/ExternalAuthenticationManagerTest.java | 183 -- ...sswordFileAuthenticationManagerFactoryTest.java | 107 - ...PrincipalDatabaseAuthenticationManagerTest.java | 348 --- .../manager/SimpleAuthenticationManagerTest.java | 160 -- ...SimpleLDAPAuthenticationManagerFactoryTest.java | 48 - .../auth/sasl/CRAMMD5HexInitialiserTest.java | 145 -- .../security/auth/sasl/CRAMMD5HexServerTest.java | 227 -- .../security/auth/sasl/SaslServerTestCase.java | 66 - .../qpid/server/security/auth/sasl/SaslUtil.java | 85 - .../security/auth/sasl/TestPrincipalDatabase.java | 107 - .../auth/sasl/amqplain/AMQPlainSaslServerTest.java | 43 - .../auth/sasl/plain/PlainSaslServerTest.java | 39 - .../security/group/FileGroupDatabaseTest.java | 456 ---- .../group/FileGroupManagerFactoryTest.java | 77 - .../security/group/FileGroupManagerTest.java | 204 -- .../server/security/group/GroupPrincipalTest.java | 88 - .../qpid/server/stats/StatisticsCounterTest.java | 149 -- .../AbstractDurableConfigurationStoreTestCase.java | 520 ----- .../apache/qpid/server/store/EventManagerTest.java | 72 - .../qpid/server/store/JsonFileConfigStoreTest.java | 299 --- .../store/MessageStoreQuotaEventsTestBase.java | 191 -- .../qpid/server/store/MessageStoreTestCase.java | 78 - .../store/OperationalLoggingListenerTest.java | 187 -- .../apache/qpid/server/store/StateManagerTest.java | 199 -- .../qpid/server/store/TestMemoryMessageStore.java | 34 - .../store/TestMemoryMessageStoreFactory.java | 54 - .../server/store/TestableMemoryMessageStore.java | 149 -- .../qpid/server/subscription/MockSubscription.java | 579 ----- .../server/subscription/SubscriptionListTest.java | 429 ---- .../server/txn/AsyncAutoCommitTransactionTest.java | 140 -- .../qpid/server/txn/AutoCommitTransactionTest.java | 442 ---- .../qpid/server/txn/LocalTransactionTest.java | 672 ------ .../org/apache/qpid/server/txn/MockAction.java | 54 - .../apache/qpid/server/txn/MockServerMessage.java | 110 - .../qpid/server/txn/MockStoreTransaction.java | 135 -- .../apache/qpid/server/util/BrokerTestHelper.java | 190 -- .../qpid/server/util/MapJsonSerializerTest.java | 53 - .../apache/qpid/server/util/StringUtilTest.java | 58 - .../DurableConfigurationRecovererTest.java | 479 ---- .../server/virtualhost/HouseKeepingTaskTest.java | 113 - .../qpid/server/virtualhost/MockVirtualHost.java | 304 --- .../virtualhost/StandardVirtualHostTest.java | 376 ---- .../org/apache/qpid/tools/security/PasswdTest.java | 38 - ...g.apache.qpid.server.plugin.MessageStoreFactory | 19 - .../qpid/server/logging/GenerateLogMessages.java | 532 ----- .../qpid/server/logging/messages/LogMessages.vm | 189 -- qpid/java/broker/src/xsl/qmf.xsl | 908 -------- 1202 files changed, 90222 insertions(+), 90222 deletions(-) create mode 100755 qpid/java/broker-core/bin/qpid-passwd create mode 100755 qpid/java/broker-core/bin/qpid-server create mode 100644 qpid/java/broker-core/bin/qpid-server.bat create mode 100755 qpid/java/broker-core/bin/qpid.stop create mode 100755 qpid/java/broker-core/bin/qpid.stopall create mode 100644 qpid/java/broker-core/build-generate-sources.xml create mode 100644 qpid/java/broker-core/build.xml create mode 100644 qpid/java/broker-core/etc/broker_example.acl create mode 100644 qpid/java/broker-core/etc/groups create mode 100644 qpid/java/broker-core/etc/log4j.xml create mode 100644 qpid/java/broker-core/etc/md5passwd create mode 100644 qpid/java/broker-core/etc/passwd create mode 100755 qpid/java/broker-core/python-test.xml create mode 100644 qpid/java/broker-core/scripts/resetAlerting.sh create mode 100755 qpid/java/broker-core/src/main/java/broker.bnd create mode 100644 qpid/java/broker-core/src/main/java/fallback-log4j.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/Broker.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/BrokerOptions.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/Main.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/TransactionTimeoutHelper.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/binding/Binding.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerConfigurationStoreCreator.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntryStore.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfiguredObjectRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/IllegalConfigurationException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/RecovererProvider.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/XmlConfigurationUtilities.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/plugins/AbstractConfiguration.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/AccessControlProviderRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProvider.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/GroupProviderRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/KeyStoreRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PluginRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PortRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PreferencesProviderRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/RecovererHelper.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/StoreUpgrader.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/TrustStoreRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/VirtualHostRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreUtil.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStore.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListener.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/factory/JsonConfigurationStoreFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/factory/MemoryConfigurationStoreFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/ChangeAttributesTask.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/ChangeStateTask.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/CreateChildTask.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/SetAttributeTask.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/TaskExecutor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchange.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DirectExchangeType.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/Exchange.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeInitialiser.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeReferrer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FanoutExchangeType.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FilterSupport.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersExchangeType.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/TopicExchangeType.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicExchangeResult.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicNormalizer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/FilterManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/MessageFilter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/AbstractRootMessageLogger.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/CompositeStartupMessageLogger.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/Log4jMessageLogger.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogMessage.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogRecorder.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogSubject.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/NullRootMessageLogger.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/RootMessageLogger.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/SystemOutMessageLogger.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AMQPChannelActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AMQPConnectionActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AbstractActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AbstractManagementActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/BrokerActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/CurrentActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/GenericActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/HttpManagementActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/QueueActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/SubscriptionActor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/log4j/LoggingFacadeException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/log4j/LoggingManagementFacade.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Binding_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Broker_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Channel_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/ConfigStore_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Connection_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Exchange_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/ManagementConsole_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/MessageStore_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Queue_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Subscription_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/TransactionLog_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/VirtualHost_logmessages.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/AbstractLogSubject.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/BindingLogSubject.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ChannelLogSubject.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubject.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubject.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubject.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/QueueLogSubject.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubject.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/AMQMessageHeader.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/AbstractServerMessageImpl.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/EnqueableMessage.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/InboundMessage.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/MessageContentSource.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/MessageReference.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/ServerMessage.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AccessControlProvider.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Attribute.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AuthenticationMethod.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AuthenticationProvider.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Binding.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfigurationChangeListener.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObject.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectFinder.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Connection.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Consumer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Event.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/EventType.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Exchange.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Group.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/GroupMember.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/GroupProvider.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/IllegalStateTransitionException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/IntegrityViolationException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/KeyStore.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/LifetimePolicy.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Model.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/PasswordCredentialManagingAuthenticationProvider.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Plugin.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Port.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/PreferencesProvider.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Protocol.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Publisher.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Queue.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/QueueNotificationListener.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/QueueType.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Session.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/State.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Statistics.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Transport.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/TrustStore.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/User.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHost.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHostAlias.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractPluginAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BindingAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ConnectionAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ConsumerAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ExchangeAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProvider.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/KeyStoreAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/NoStatistics.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PreferencesProviderCreator.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/QueueAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/SessionAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/StatisticsAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/TrustStoreAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAliasAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/AccessControlFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/AuthenticationManagerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ConfigurationStoreFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/DurableConfigurationStoreFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ExchangeType.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/GroupManagerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/JDBCConnectionProviderFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageConverter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageMetaDataType.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageStoreFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/Pluggable.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PluginFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PreferencesProviderFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ProtocolEngineCreator.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/QpidServiceLoader.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/VirtualHostFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AMQConnectionModel.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AMQSessionModel.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AmqpProtocolVersion.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/LinkModel.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/LinkRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MessageConverterRegistry.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngineFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQQueue.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/BaseQueue.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/ConflationQueue.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/ConflationQueueList.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/Filterable.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/InboundMessageAdapter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/OutOfOrderQueue.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueArgumentsConverter.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueContext.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryVisitor.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueRunner.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryImpl.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueue.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryImpl.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryList.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryListFactory.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SubFlushRunner.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/AccessControl.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/AuthorizationHolder.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/Result.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SecurityManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SubjectCreator.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/FileAccessControlProviderConstants.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/ObjectType.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/Operation.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/OperationLoggingDetails.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/Permission.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipal.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/SubjectAuthenticationResult.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PasswordPrincipal.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/jmx/JMXPasswordAuthenticator.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractPrincipalDatabaseAuthManagerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/Base64MD5PasswordFileAuthenticationManagerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationProviderAttributeDescriptions.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManagerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PasswordFileAuthenticationProviderAttributeDescriptions.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PlainPasswordFileAuthenticationManagerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationProviderAttributeDescriptions.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexSaslServer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexServerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/external/ExternalSaslServer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainPasswordCallback.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupDatabase.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupManagerFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupProviderAttributeDescriptions.properties create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupDatabase.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupPrincipal.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/stats/StatisticsCounter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/stats/StatisticsGatherer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractDurableConfiguredObjectRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractJDBCMessageStore.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractMemoryMessageStore.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfiguredObjectRecord.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DependencyListener.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationRecoverer.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreCreator.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreHelper.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreUpgrader.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfiguredObjectRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/Event.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/EventListener.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/EventManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/HAMessageStore.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStore.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStoreFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageMetaDataTypeRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStore.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreConstants.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreCreator.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreRecoveryHandler.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NonNullUpgrader.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullMessageStore.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullUpgrader.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/OperationalLoggingListener.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/State.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StateManager.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StorableMessageMetaData.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoreContext.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoreFuture.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoredMemoryMessage.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoredMessage.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/Transaction.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLog.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLogRecoveryHandler.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLogResource.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedDependency.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedObject.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UpgraderProvider.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/jdbc/ConnectionProvider.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/AssignedSubscriptionMessageGroupManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/DefinedGroupMessageGroupManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/MessageGroupManager.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/Subscription.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AlreadyKnownDtxException.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AsyncAutoCommitTransaction.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AutoCommitTransaction.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DistributedTransaction.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxBranch.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxNotSelectedException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/IncorrectDtxStateException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/JoinAndResumeDtxException.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/LocalTransaction.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/NotAssociatedDtxException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/RollbackOnlyDtxException.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/ServerTransaction.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/SuspendAndFailDtxException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/TimeoutDtxException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/UnknownDtxBranchException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ByteBufferOutputStream.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapJsonSerializer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapValueConverter.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ParameterizedTypeImpl.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ResourceBundleLoader.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/StringUtil.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/BindingRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/DefaultUpgraderProvider.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeExistsException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeIsAlternateException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/HouseKeepingTask.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/QueueRecoverer.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/RequiredExchangeException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ReservedExchangeNameException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHost.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHostFactory.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/State.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/UnknownExchangeException.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java create mode 100755 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostConfigRecoveryHandler.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostFactoryRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostListener.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/plugins/QueueExistsException.java create mode 100644 qpid/java/broker-core/src/main/java/org/apache/qpid/tools/security/Passwd.java create mode 100644 qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.AuthenticationManagerFactory create mode 100644 qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ConfigurationStoreFactory create mode 100644 qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.DurableConfigurationStoreFactory create mode 100644 qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ExchangeType create mode 100644 qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.GroupManagerFactory create mode 100644 qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.JDBCConnectionProviderFactory create mode 100644 qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.PreferencesProviderFactory create mode 100644 qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.VirtualHostFactory create mode 100644 qpid/java/broker-core/src/main/resources/initial-config.json create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/BrokerOptionsTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/MainTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/SelectorParserTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/TransactionTimeoutHelperTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/BrokerConfigurationStoreCreatorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/BrokerPropertiesTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/plugins/AbstractConfigurationTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecovererTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/BrokerRecovererTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProviderTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/GroupProviderRecovererTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/KeyStoreRecovererTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/PluginRecovererTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/PreferencesProviderRecovererTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/TrustStoreRecovererTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/VirtualHostRecovererTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandlerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListenerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/updater/TaskExecutorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/DefaultExchangeFactoryTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/FanoutExchangeTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/TopicExchangeTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/filter/JMSSelectorFilterTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/Log4jMessageLoggerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/LogMessageTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLogger.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLoggerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AMQPChannelActorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AMQPConnectionActorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AbstractManagementActorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/BaseActorTestCase.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/BaseConnectionActorTestCase.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/CurrentActorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/HttpManagementActorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/QueueActorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/SubscriptionActorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/TestLogActor.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/log4j/LoggingFacadeTest.log4j.xml create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/log4j/LoggingManagementFacadeTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/AbstractTestMessages.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/BindingMessagesTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/BrokerMessagesTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ChannelMessagesTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ConnectionMessagesTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ExchangeMessagesTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ManagementConsoleMessagesTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/MessageStoreMessagesTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/QueueMessagesTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/SubscriptionMessagesTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/VirtualHostMessagesTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/AbstractTestLogSubject.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/BindingLogSubjectTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ChannelLogSubjectTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubjectTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubjectTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubjectTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/QueueLogSubjectTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubjectTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/TestBlankSubject.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/ConfiguredObjectStateTransitionTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/UUIDGeneratorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/VirtualHostTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactoryTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderFactoryTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/configuration/ConfigurationEntryTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/ConflationQueueListTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/InboundMessageAdapterTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/NotificationCheckTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/PriorityQueueListTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/QueueEntryImplTestBase.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/QueueEntryListTestBase.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SelfValidatingSortedQueueEntryList.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryImplTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryListTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SortedQueueEntryImplTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SortedQueueEntryListTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/SubjectCreatorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTestHelper.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticationResultTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/TestPrincipalUtils.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/UsernamePrincipalTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/jmx/JMXPasswordAuthenticatorTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/Base64MD5PasswordFileAuthenticationManagerFactoryTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/PlainPasswordFileAuthenticationManagerFactoryTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManagerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerFactoryTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexInitialiserTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslUtil.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupDatabaseTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerFactoryTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/GroupPrincipalTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/stats/StatisticsCounterTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/AbstractDurableConfigurationStoreTestCase.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/EventManagerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/JsonFileConfigStoreTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreQuotaEventsTestBase.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreTestCase.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/OperationalLoggingListenerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/StateManagerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStoreFactory.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/SubscriptionListTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/AsyncAutoCommitTransactionTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/AutoCommitTransactionTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/LocalTransactionTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockAction.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockServerMessage.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockStoreTransaction.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/BrokerTestHelper.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/MapJsonSerializerTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/StringUtilTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/DurableConfigurationRecovererTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/HouseKeepingTaskTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/MockVirtualHost.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/StandardVirtualHostTest.java create mode 100644 qpid/java/broker-core/src/test/java/org/apache/qpid/tools/security/PasswdTest.java create mode 100644 qpid/java/broker-core/src/test/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory create mode 100644 qpid/java/broker-core/src/velocity/java/org/apache/qpid/server/logging/GenerateLogMessages.java create mode 100644 qpid/java/broker-core/src/velocity/templates/org/apache/qpid/server/logging/messages/LogMessages.vm create mode 100644 qpid/java/broker-core/src/xsl/qmf.xsl delete mode 100755 qpid/java/broker/bin/qpid-passwd delete mode 100755 qpid/java/broker/bin/qpid-server delete mode 100644 qpid/java/broker/bin/qpid-server.bat delete mode 100755 qpid/java/broker/bin/qpid.stop delete mode 100755 qpid/java/broker/bin/qpid.stopall delete mode 100644 qpid/java/broker/build-generate-sources.xml delete mode 100644 qpid/java/broker/build.xml delete mode 100644 qpid/java/broker/etc/broker_example.acl delete mode 100644 qpid/java/broker/etc/groups delete mode 100644 qpid/java/broker/etc/log4j.xml delete mode 100644 qpid/java/broker/etc/md5passwd delete mode 100644 qpid/java/broker/etc/passwd delete mode 100755 qpid/java/broker/python-test.xml delete mode 100644 qpid/java/broker/scripts/resetAlerting.sh delete mode 100755 qpid/java/broker/src/main/java/broker.bnd delete mode 100644 qpid/java/broker/src/main/java/fallback-log4j.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/Broker.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/BrokerOptions.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/TransactionTimeoutHelper.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/binding/Binding.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerConfigurationStoreCreator.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntryStore.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfiguredObjectRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/IllegalConfigurationException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/RecovererProvider.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/XmlConfigurationUtilities.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/AbstractConfiguration.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/AccessControlProviderRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProvider.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/GroupProviderRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/KeyStoreRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/PluginRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/PortRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/PreferencesProviderRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/RecovererHelper.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/StoreUpgrader.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/TrustStoreRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/startup/VirtualHostRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreUtil.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStore.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListener.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/factory/JsonConfigurationStoreFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/store/factory/MemoryConfigurationStoreFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/updater/ChangeAttributesTask.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/updater/ChangeStateTask.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/updater/CreateChildTask.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/updater/SetAttributeTask.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/updater/TaskExecutor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchange.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchangeType.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInitialiser.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeReferrer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchangeType.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FilterSupport.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchangeType.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchangeType.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicExchangeResult.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicNormalizer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/AbstractRootMessageLogger.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/CompositeStartupMessageLogger.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/Log4jMessageLogger.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogMessage.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogRecorder.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogSubject.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/NullRootMessageLogger.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/RootMessageLogger.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/SystemOutMessageLogger.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AMQPChannelActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AMQPConnectionActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AbstractActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AbstractManagementActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/BrokerActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/CurrentActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/GenericActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/HttpManagementActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/QueueActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/SubscriptionActor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/log4j/LoggingFacadeException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/log4j/LoggingManagementFacade.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Binding_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Broker_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Channel_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/ConfigStore_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Connection_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Exchange_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/ManagementConsole_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/MessageStore_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Queue_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Subscription_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/TransactionLog_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/VirtualHost_logmessages.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/AbstractLogSubject.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/BindingLogSubject.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ChannelLogSubject.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubject.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubject.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubject.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/QueueLogSubject.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubject.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/message/AMQMessageHeader.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/message/AbstractServerMessageImpl.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/message/EnqueableMessage.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/message/InboundMessage.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageContentSource.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageReference.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/message/ServerMessage.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/AccessControlProvider.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Attribute.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/AuthenticationMethod.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/AuthenticationProvider.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Binding.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Broker.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/ConfigurationChangeListener.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/ConfiguredObject.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/ConfiguredObjectFinder.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Connection.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Consumer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Event.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/EventType.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Exchange.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Group.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/GroupMember.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/GroupProvider.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/IllegalStateTransitionException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/IntegrityViolationException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/KeyStore.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/LifetimePolicy.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Model.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/PasswordCredentialManagingAuthenticationProvider.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Plugin.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Port.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/PreferencesProvider.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Protocol.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Publisher.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Queue.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/QueueNotificationListener.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/QueueType.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Session.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/State.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Statistics.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/Transport.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/TrustStore.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/User.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/VirtualHost.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/VirtualHostAlias.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AbstractPluginAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BindingAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/ConnectionAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/ConsumerAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/ExchangeAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProvider.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/KeyStoreAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/NoStatistics.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PortFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/PreferencesProviderCreator.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/QueueAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/SessionAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/StatisticsAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/TrustStoreAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAliasAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/AccessControlFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/AuthenticationManagerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/ConfigurationStoreFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/DurableConfigurationStoreFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/ExchangeType.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/GroupManagerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/JDBCConnectionProviderFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/MessageConverter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/MessageMetaDataType.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/MessageStoreFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/Pluggable.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/PluginFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/PreferencesProviderFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/ProtocolEngineCreator.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/QpidServiceLoader.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/plugin/VirtualHostFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQConnectionModel.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQSessionModel.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AmqpProtocolVersion.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/LinkModel.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/LinkRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/MessageConverterRegistry.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngineFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/BaseQueue.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConflationQueue.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConflationQueueList.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Filterable.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InboundMessageAdapter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/OutOfOrderQueue.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueArgumentsConverter.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueContext.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryVisitor.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRunner.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryImpl.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SortedQueue.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryImpl.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryList.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryListFactory.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubFlushRunner.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/AccessControl.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/security/AuthorizationHolder.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/Result.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/SubjectCreator.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/FileAccessControlProviderConstants.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/OperationLoggingDetails.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipal.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/SubjectAuthenticationResult.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PasswordPrincipal.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/jmx/JMXPasswordAuthenticator.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractPrincipalDatabaseAuthManagerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/Base64MD5PasswordFileAuthenticationManagerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationProviderAttributeDescriptions.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManagerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PasswordFileAuthenticationProviderAttributeDescriptions.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PlainPasswordFileAuthenticationManagerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationProviderAttributeDescriptions.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexSaslServer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexServerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/external/ExternalSaslServer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainPasswordCallback.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/group/FileGroupDatabase.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/group/FileGroupManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/group/FileGroupManagerFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/group/FileGroupProviderAttributeDescriptions.properties delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupDatabase.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/security/group/GroupPrincipal.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/stats/StatisticsCounter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/stats/StatisticsGatherer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/AbstractDurableConfiguredObjectRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/AbstractJDBCMessageStore.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/AbstractMemoryMessageStore.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/ConfiguredObjectRecord.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/DependencyListener.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/DurableConfigurationRecoverer.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreCreator.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreHelper.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreUpgrader.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/DurableConfiguredObjectRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/Event.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/EventListener.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/EventManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/HAMessageStore.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/JsonFileConfigStore.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/JsonFileConfigStoreFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageMetaDataTypeRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreConstants.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreCreator.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreRecoveryHandler.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/NonNullUpgrader.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/NullMessageStore.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/NullUpgrader.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/OperationalLoggingListener.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/State.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/StateManager.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/store/StorableMessageMetaData.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreFuture.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoredMemoryMessage.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoredMessage.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/Transaction.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLog.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLogRecoveryHandler.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLogResource.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/UnresolvedDependency.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/UnresolvedObject.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/UpgraderProvider.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/store/jdbc/ConnectionProvider.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/AssignedSubscriptionMessageGroupManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/DefinedGroupMessageGroupManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/MessageGroupManager.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/AlreadyKnownDtxException.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/AsyncAutoCommitTransaction.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/AutoCommitTransaction.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/DistributedTransaction.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/DtxBranch.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/DtxException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/DtxNotSelectedException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/DtxRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/IncorrectDtxStateException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/JoinAndResumeDtxException.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransaction.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/NotAssociatedDtxException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/RollbackOnlyDtxException.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/ServerTransaction.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/SuspendAndFailDtxException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/TimeoutDtxException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/txn/UnknownDtxBranchException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/util/ByteBufferOutputStream.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/util/MapJsonSerializer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/util/MapValueConverter.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/util/ParameterizedTypeImpl.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/util/ResourceBundleLoader.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/util/StringUtil.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/BindingRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/DefaultUpgraderProvider.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ExchangeExistsException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ExchangeIsAlternateException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ExchangeRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/HouseKeepingTask.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/QueueRecoverer.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/RequiredExchangeException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ReservedExchangeNameException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHost.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHostFactory.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/State.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/UnknownExchangeException.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java delete mode 100755 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostConfigRecoveryHandler.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostFactoryRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostListener.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/QueueExistsException.java delete mode 100644 qpid/java/broker/src/main/java/org/apache/qpid/tools/security/Passwd.java delete mode 100644 qpid/java/broker/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.AuthenticationManagerFactory delete mode 100644 qpid/java/broker/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ConfigurationStoreFactory delete mode 100644 qpid/java/broker/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.DurableConfigurationStoreFactory delete mode 100644 qpid/java/broker/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ExchangeType delete mode 100644 qpid/java/broker/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.GroupManagerFactory delete mode 100644 qpid/java/broker/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.JDBCConnectionProviderFactory delete mode 100644 qpid/java/broker/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.PreferencesProviderFactory delete mode 100644 qpid/java/broker/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.VirtualHostFactory delete mode 100644 qpid/java/broker/src/main/resources/initial-config.json delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/BrokerOptionsTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/MainTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/SelectorParserTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/TransactionTimeoutHelperTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/BrokerConfigurationStoreCreatorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/BrokerPropertiesTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/plugins/AbstractConfigurationTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecovererTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/BrokerRecovererTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProviderTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/GroupProviderRecovererTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/KeyStoreRecovererTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/PluginRecovererTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/PreferencesProviderRecovererTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/TrustStoreRecovererTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/startup/VirtualHostRecovererTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandlerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListenerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/configuration/updater/TaskExecutorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/DefaultExchangeFactoryTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/FanoutExchangeTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/exchange/TopicExchangeTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/filter/JMSSelectorFilterTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/Log4jMessageLoggerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/LogMessageTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLogger.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLoggerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AMQPChannelActorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AMQPConnectionActorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/AbstractManagementActorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/BaseActorTestCase.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/BaseConnectionActorTestCase.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/CurrentActorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/HttpManagementActorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/QueueActorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/SubscriptionActorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/actors/TestLogActor.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/log4j/LoggingFacadeTest.log4j.xml delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/log4j/LoggingManagementFacadeTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/AbstractTestMessages.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/BindingMessagesTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/BrokerMessagesTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ChannelMessagesTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ConnectionMessagesTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ExchangeMessagesTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/ManagementConsoleMessagesTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/MessageStoreMessagesTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/QueueMessagesTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/SubscriptionMessagesTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/messages/VirtualHostMessagesTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/AbstractTestLogSubject.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/BindingLogSubjectTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ChannelLogSubjectTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubjectTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubjectTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubjectTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/QueueLogSubjectTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubjectTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/logging/subjects/TestBlankSubject.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/model/ConfiguredObjectStateTransitionTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/model/UUIDGeneratorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/model/VirtualHostTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactoryTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderFactoryTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/model/configuration/ConfigurationEntryTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/ConflationQueueListTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/InboundMessageAdapterTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/NotificationCheckTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/PriorityQueueListTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/QueueEntryImplTestBase.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/QueueEntryListTestBase.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SelfValidatingSortedQueueEntryList.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryImplTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryListTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SortedQueueEntryImplTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/queue/SortedQueueEntryListTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/SubjectCreatorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTestHelper.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/AuthenticationResultTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/TestPrincipalUtils.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/UsernamePrincipalTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/jmx/JMXPasswordAuthenticatorTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/Base64MD5PasswordFileAuthenticationManagerFactoryTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PlainPasswordFileAuthenticationManagerFactoryTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManagerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerFactoryTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexInitialiserTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslUtil.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/group/FileGroupDatabaseTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerFactoryTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/security/group/GroupPrincipalTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/stats/StatisticsCounterTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/store/AbstractDurableConfigurationStoreTestCase.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/store/EventManagerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/store/JsonFileConfigStoreTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreQuotaEventsTestBase.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/store/MessageStoreTestCase.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/store/OperationalLoggingListenerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/store/StateManagerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStoreFactory.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/subscription/SubscriptionListTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/txn/AsyncAutoCommitTransactionTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/txn/AutoCommitTransactionTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/txn/LocalTransactionTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockAction.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockServerMessage.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/txn/MockStoreTransaction.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/util/BrokerTestHelper.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/util/MapJsonSerializerTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/util/StringUtilTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/DurableConfigurationRecovererTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/HouseKeepingTaskTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/MockVirtualHost.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/server/virtualhost/StandardVirtualHostTest.java delete mode 100644 qpid/java/broker/src/test/java/org/apache/qpid/tools/security/PasswdTest.java delete mode 100644 qpid/java/broker/src/test/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory delete mode 100644 qpid/java/broker/src/velocity/java/org/apache/qpid/server/logging/GenerateLogMessages.java delete mode 100644 qpid/java/broker/src/velocity/templates/org/apache/qpid/server/logging/messages/LogMessages.vm delete mode 100644 qpid/java/broker/src/xsl/qmf.xsl diff --git a/qpid/java/broker-core/bin/qpid-passwd b/qpid/java/broker-core/bin/qpid-passwd new file mode 100755 index 0000000000..69246974fa --- /dev/null +++ b/qpid/java/broker-core/bin/qpid-passwd @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +if [ -z "$QPID_HOME" ]; then + WHEREAMI=`dirname "$0"` + export QPID_HOME=`cd "$WHEREAMI/../" && pwd` +fi + +# Set classpath to include Qpid jar with all required jars in manifest +QPID_LIBS=$QPID_HOME/lib/qpid-all.jar + +# Set other variables used by the qpid-run script before calling +export JAVA=java \ + JAVA_VM=-server \ + JAVA_MEM=-Xmx1024m \ + QPID_CLASSPATH=$QPID_LIBS + +. "${QPID_HOME}/bin/qpid-run" org.apache.qpid.tools.security.Passwd "$@" diff --git a/qpid/java/broker-core/bin/qpid-server b/qpid/java/broker-core/bin/qpid-server new file mode 100755 index 0000000000..206ae6a225 --- /dev/null +++ b/qpid/java/broker-core/bin/qpid-server @@ -0,0 +1,56 @@ +#!/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. +# +WHEREAMI=`dirname $0` +if [ -z "$QPID_HOME" ]; then + export QPID_HOME=`cd $WHEREAMI/../ && pwd` +fi + +if [ -z "$QPID_WORK" ]; then + echo "Setting QPID_WORK to $HOME as default" + QPID_WORK=$HOME +fi + +# Set to help us get round the manifold problems of ps/pgrep on various +# platforms which gather up to prevent qpid_stop from working ..... +if [ -z "$QPID_PNAME" ]; then + export QPID_PNAME=" -DPNAME=QPBRKR" +fi + +# Set classpath to include the qpid-all manifest jar, plus jars in lib/plugins and lib/opt +QPID_LIBS="$QPID_HOME/lib/qpid-all.jar:$QPID_HOME/lib/plugins/*:$QPID_HOME/lib/opt/*" + +# Set other variables used by the qpid-run script before calling +export JAVA=java \ + JAVA_VM=-server \ + JAVA_MEM=-Xmx1024m \ + JAVA_GC="-XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError" \ + QPID_CLASSPATH=$QPID_LIBS \ + QPID_RUN_LOG=2 + +QPID_OPTS="$QPID_OPTS -Damqj.read_write_pool_size=32 -DQPID_LOG_APPEND=$QPID_LOG_APPEND" + +# Echo the PID to file. Since qpid-run is sourced and uses exec to +# launch the broker, this will give the brokers PID. +if [ -z "$QPID_PID_FILENAME" ]; then + export QPID_PID_FILENAME="qpid-server.pid" +fi +echo $$ > "${QPID_WORK}/${QPID_PID_FILENAME}" + +. "${QPID_HOME}/bin/qpid-run" org.apache.qpid.server.Main "$@" diff --git a/qpid/java/broker-core/bin/qpid-server.bat b/qpid/java/broker-core/bin/qpid-server.bat new file mode 100644 index 0000000000..96965b0b42 --- /dev/null +++ b/qpid/java/broker-core/bin/qpid-server.bat @@ -0,0 +1,201 @@ +@REM +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM + +@echo off +REM Script to run the Qpid Java Broker + +rem Guess QPID_HOME if not defined +set CURRENT_DIR=%cd% +if not "%QPID_HOME%" == "" goto gotHome +set QPID_HOME=%CURRENT_DIR% +echo %QPID_HOME% +if exist "%QPID_HOME%\bin\qpid-server.bat" goto okHome +cd .. +set QPID_HOME=%cd% +cd %CURRENT_DIR% +:gotHome +if exist "%QPID_HOME%\bin\qpid-server.bat" goto okHome +echo The QPID_HOME environment variable is not defined correctly +echo This environment variable is needed to run this program +goto end +:okHome + +REM set QPID_WORK if not set +if not "%QPID_WORK%" == "" goto okQpidWork +if "%HOME%" == "" goto noHome +set QPID_WOKR=%HOME% +goto okQpidWork + +:noHome +set QPID_WORK=c:\Temp +if not exist %QPID_WORK% md %QPID_WORK% +:okQpidWork + +if not "%JAVA_HOME%" == "" goto gotJavaHome +echo The JAVA_HOME environment variable is not defined +echo This environment variable is needed to run this program +goto end +:gotJavaHome +if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome +goto okJavaHome +:noJavaHome +echo The JAVA_HOME environment variable is not defined correctly +echo This environment variable is needed to run this program. +goto end +:okJavaHome + +REM set loggin level if not set +if "%AMQJ_LOGGING_LEVEL%" == "" set AMQJ_LOGGING_LEVEL=info + +REM Set the default system properties that we'll use now that they have +REM all been initialised +set SYSTEM_PROPS=-Damqj.logging.level=%AMQJ_LOGGING_LEVEL% -DQPID_HOME="%QPID_HOME%" -DQPID_WORK="%QPID_WORK%" + +if "%EXTERNAL_CLASSPATH%" == "" set EXTERNAL_CLASSPATH=%CLASSPATH% + +REM Use QPID_CLASSPATH if set +if "%QPID_CLASSPATH%" == "" goto noQpidClasspath +set CLASSPATH=%QPID_CLASSPATH% +echo Using CLASSPATH: %CLASSPATH% +goto afterQpidClasspath + +:noQpidClasspath +echo Warning: Qpid classpath not set. CLASSPATH set to %QPID_HOME%\lib\qpid-all.jar;%QPID_HOME%\lib\plugins\*;%QPID_HOME%\lib\opt\* +set CLASSPATH=%QPID_HOME%\lib\qpid-all.jar;%QPID_HOME%\lib\plugins\*;%QPID_HOME%\lib\opt\* +:afterQpidClasspath + +REM start parsing -run arguments +set QPID_ARGS= +if "%1" == "" goto endRunArgs +:runLoop +set var=%~1 +if "%var:~0,5%" == "-run:" goto runFound +set QPID_ARGS=%QPID_ARGS% %1 +:beforeRunShift +shift +if "%~1" neq "" goto runLoop +goto endRunArgs + +:runFound +if "%var%" == "-run:debug" goto runDebug +if "%var%" == "-run:jpda" goto runJdpa +if "%var:~0,24%" == "-run:external-classpath-" goto runExternalClasspath +if "%var%" == "-run:print-classpath" goto runPrintCP +if "%var%" == "-run:help" goto runHelp +echo "unrecognized -run option '%var%'. For using external classpaths use -run:external-classpath-option" +goto end + +:runDebug +REM USAGE: print the classpath and command before running it +set debug=true +goto beforeRunShift + +:runJdpa +REM USAGE: adds debugging options to the java command, use +REM USAGE: JPDA_TRANSPORT and JPDA_ADDRESS to customize the debugging +REM USAGE: behavior and use JPDA_OPTS to override it entirely +if not "%JPDA_OPTS%" == "" goto beforeRunShift +if "%JPDA_TRANSPORT%" == "" set JPDA_TRANSPORT=dt_socket +if "%JPDA_ADDRESS%" == "" set JPDA_ADDRESS=8000 +set JPDA_OPTS=-Xdebug -Xrunjdwp:transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=n +REM set QPID_OPTS="%QPID_OPTS% %JPDA_OPTS%" +goto beforeRunShift + +:runExternalClasspath +echo Using external classpath %var% +REM USAGE: Format is -run:external-classpath-first/last/ignore/only as equals special in DOS +REM USAGE: controls how the CLASSPATH environment variable is used by +REM USAGE: this script, value can be one of ignore (the default), first, +REM USAGE: last, and only +if "%var%" == "-run:external-classpath-ignore" goto beforeRunShift +if "%var%" == "-run:external-classpath-first" goto extCPFirst +if "%var%" == "-run:external-classpath-last" goto extCPLast +if "%var%" == "-run:external-classpath-only" goto extCPOnly +echo Invalid value provided for external classpath. +goto end + +:extCPFirst +set CLASSPATH=%EXTERNAL_CLASSPATH%;%CLASSPATH% +goto beforeRunShift + +:extCPLast +set CLASSPATH=%CLASSPATH%;%EXTERNAL_CLASSPATH% +goto beforeRunShift + +:extCPonly +set CLASSPATH=%EXTERNAL_CLASSPATH% +goto beforeRunShift + +:runPrintCP +REM USAGE: print the classpath +echo %CLASSPATH% +goto beforeRunShift + +:runHelp +REM USAGE: print this message +echo ------------------------------------------------------------------------------------------- +echo -run:option where option can be the following. +echo debug : Prints classpath and command before running it +echo jpda : Adds remote debugging info using JPDA_OPTS. Use JPDA_TRANSPORT and JPDA_ADDRESS to +echo customize, JPDA_OPTS to override +echo external-classpath : Valid values are: ignore, first, last and only. +echo print-classpath : Prints classpath before running command +echo help : Prints this message +echo -------------------------------------------------------------------------------------------- +goto end + +REM end parsing -run arguments +:endRunArgs + +set JAVA_VM=-server +set JAVA_MEM=-Xmx1024m +set JAVA_GC=-XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError + +REM Use QPID_JAVA_GC if set +if "%QPID_JAVA_GC%" == "" goto noQpidJavaGC +set JAVA_GC=%QPID_JAVA_GC% +echo Using QPID_JAVA_GC setting: %QPID_JAVA_GC% +goto afteQpidJavaGC + +:noQPidJavaGC +echo Info: QPID_JAVA_GC not set. Defaulting to JAVA_GC %JAVA_GC% +:afterQpidJavaGC + +REM Use QPID_JAVA_MEM if set +if "%QPID_JAVA_MEM%" == "" goto noQpidJavaMem +set JAVA_MEM=%QPID_JAVA_MEM% +echo Using QPID_JAVA_MEM setting: %QPID_JAVA_MEM% +goto afterQpidJavaMem + +:noQpidJavaMem +echo Info: QPID_JAVA_MEM not set. Defaulting to JAVA_MEM %JAVA_MEM% +:afterQpidJavaMem + + +rem QPID_OPTS intended to hold any -D props for use +rem user must enclose any value for QPID_OPTS in double quotes +:runCommand +set MODULE_JARS=%QPID_MODULE_JARS% +set COMMAND="%JAVA_HOME%\bin\java" %JAVA_VM% %JAVA_MEM% %JAVA_GC% %QPID_OPTS% %JPDA_OPTS% %SYSTEM_PROPS% -cp "%CLASSPATH%;%MODULE_JARS%" org.apache.qpid.server.Main %QPID_ARGS% + +if "%debug%" == "true" echo %CLASSPATH%;%LAUNCH_JAR%;%MODULE_JARS% +if "%debug%" == "true" echo %COMMAND% +%COMMAND% + +:end diff --git a/qpid/java/broker-core/bin/qpid.stop b/qpid/java/broker-core/bin/qpid.stop new file mode 100755 index 0000000000..316f8dff46 --- /dev/null +++ b/qpid/java/broker-core/bin/qpid.stop @@ -0,0 +1,178 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# qpid.stop Script +# +# Script checks for a given pid running DEFAULT_SEARCH and attempts to quit it +# + +MAX_ATTEMPTS=2 +SLEEP_DELAY=1 +DEFAULT_SEARCH="PNAME=QPBRKR" + +if [ -z "$QPID_STOP_SEARCH" ]; then + SEARCH=$DEFAULT_SEARCH; +else + SEARCH=$QPID_STOP_SEARCH; +fi + +# +# Forcably Quit the specified PID($1) +# +forceQuit() +{ +kill -9 $1 +} + +# +# Gracefully ask the PID($1) to quit +# +quit() +{ +kill $1 +} + +# +# grep for the session ID ($1) and return 0 for successful quit and 1 for process alive +# +lookup_pid() +{ +result=`ps -e | grep $1 | wc -l` +} + +# +# grep ps for all instances of $SEARCH for the current user and collect PIDs +# +lookup_all_pids() +{ +pids=`pgrep -f -U $USER $SEARCH` +result_all=`echo -n $pids | wc -w` +} + +# +# check that the PID passed in is for a Qpid broker owned by this user and alive +# +validate_pid() +{ +result=`pgrep -fl $SEARCH | grep $1 | wc -l` +} + +# +# Show the PS output for given set of pids +# +showPids() +{ +ps -o user,pid,args -p $pids +} + +# +# Sleep and then check then lookup the PID($1) to ensure it has quit +# +check() +{ +echo "Waiting $SLEEP_DELAY second for $1 to exit" +sleep $SLEEP_DELAY +lookup_pid $1 +} + +# +# Verify the PID($1) is available +# +verifyPid() +{ +validate_pid $1 +if [[ $[$result] == 1 ]] ; then + brokerspid=$1 +else + echo "Unable to locate Qpid Broker Process with PID $1. Check PID and try again." + exit -1 +fi +} + +# +# Stops all Qpid brokers for current user +# +qpid_stopall_brokers() +{ +for pid in $pids ; do + lookup_pid $pid; + brokerspid=$pid; + stop_broker $pid; +done +} + +# +# Stops Qpid broker with brokerspid id +# +stop_broker() +{ +# Attempt to quit the process MAX_ATTEMPTS Times +attempt=0 +while [[ $[$result] > 0 && $[$attempt] < $[$MAX_ATTEMPTS] ]] ; do + quit $brokerspid + check $brokerspid + attempt=$[$attempt + 1] +done + +# Check that it has quit +if [[ $[$result] == 0 ]] ; then + echo "Process quit" +else + + attempt=0 + # Now attempt to force quit the process + while [[ $[$result] > 0 && $[$attempt] < $[$MAX_ATTEMPTS] ]] ; do + forceQuit $brokerspid + check $brokerspid + attempt=$[$attempt + 1] + done + + # Output final status + if [[ $[$result] > 0 && $[$attempt] == $[$MAX_ATTEMPTS] ]] ; then + echo "Stopped trying to kill process: $brokerspid" + echo "Attempted to stop $attempt times" + else + echo "Done " + fi +fi + +} + +# +# Main Run +# + +# Check if we are killing all qpid pids or just one. +# Now uses local function qpid_stopall_brokers +if [[ $# == 0 ]] ; then + lookup_all_pids + if [[ $[$result_all] > 0 ]] ; then + echo "Killing All Qpid Brokers for user: '$USER'" + qpid_stopall_brokers + else + echo "No Qpid Brokers found running for user: " $USER + fi + exit $result +else + verifyPid $1 + stop_broker + exit $result +fi + diff --git a/qpid/java/broker-core/bin/qpid.stopall b/qpid/java/broker-core/bin/qpid.stopall new file mode 100755 index 0000000000..b0ad506629 --- /dev/null +++ b/qpid/java/broker-core/bin/qpid.stopall @@ -0,0 +1,27 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# qpid.stopall script +# +# Script attempts to stop all PROGRAMs running under the current user +# Utilises qpid.stop to perform the actual stopping +# + +qpid.stop $* diff --git a/qpid/java/broker-core/build-generate-sources.xml b/qpid/java/broker-core/build-generate-sources.xml new file mode 100644 index 0000000000..2e46a43210 --- /dev/null +++ b/qpid/java/broker-core/build-generate-sources.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qpid/java/broker-core/build.xml b/qpid/java/broker-core/build.xml new file mode 100644 index 0000000000..50b637e5c3 --- /dev/null +++ b/qpid/java/broker-core/build.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qpid/java/broker-core/etc/broker_example.acl b/qpid/java/broker-core/etc/broker_example.acl new file mode 100644 index 0000000000..29dca90f15 --- /dev/null +++ b/qpid/java/broker-core/etc/broker_example.acl @@ -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. +# + +### EXAMPLE ACL V2 FILE +### NOTE: Rules are considered from top to bottom, and the first matching rule governs the decision. +### Rules may refer to users or groups. Groups are currently defined in the etc/groups file. + +### JMX MANAGEMENT #### + +# To use JMX management, first give the user/group ACCESS MANAGEMENT permission +ACL ALLOW administrators ACCESS MANAGEMENT +ACL ALLOW guest ACCESS MANAGEMENT + +# Allow guest to perform read operations on the ServerInformation mbean +ACL ALLOW guest ACCESS METHOD component="ServerInformation" + +# Allow 'administrators' all management operations. To reduce log file noise, only non-read-only operations are logged. +ACL ALLOW administrators ACCESS METHOD +ACL ALLOW-LOG administrators ALL METHOD + +# Allow 'guest' to view logger levels, and use getter methods on LoggingManagement +ACL ALLOW guest ACCESS METHOD component="LoggingManagement" name="viewEffectiveRuntimeLoggerLevels" +ACL ALLOW guest ACCESS METHOD component="LoggingManagement" name="get*" + +# Deny access to Shutdown, UserManagement, ConfigurationManagement and LoggingManagement for all other users +# You could grant specific users access to these beans by adding rules above to allow them +ACL DENY-LOG ALL ACCESS METHOD component="Shutdown" +ACL DENY-LOG ALL ACCESS METHOD component="UserManagement" +ACL DENY-LOG ALL ACCESS METHOD component="ConfigurationManagement" +ACL DENY-LOG ALL ACCESS METHOD component="LoggingManagement" + +# Allow everyone to perform all read operations on the mbeans not listed in the DENY rules above +ACL ALLOW ALL ACCESS METHOD + +### WEB MANAGEMENT #### + +# To use web management, first give the user/group ACCESS MANAGEMENT permission +ACL ALLOW webadmins ACCESS MANAGEMENT + +# ACL for web management console admins +# All rules below are required for console admin users +# to perform create/update/delete operations +ACL ALLOW-LOG webadmins CREATE QUEUE +ACL ALLOW-LOG webadmins UPDATE QUEUE +ACL ALLOW-LOG webadmins DELETE QUEUE +ACL ALLOW-LOG webadmins PURGE QUEUE +ACL ALLOW-LOG webadmins CREATE EXCHANGE +ACL ALLOW-LOG webadmins DELETE EXCHANGE +ACL ALLOW-LOG webadmins BIND EXCHANGE +ACL ALLOW-LOG webadmins UNBIND EXCHANGE +ACL ALLOW-LOG webadmins CREATE GROUP +ACL ALLOW-LOG webadmins DELETE GROUP +ACL ALLOW-LOG webadmins UPDATE GROUP +ACL ALLOW-LOG webadmins CREATE USER +ACL ALLOW-LOG webadmins DELETE USER +ACL ALLOW-LOG webadmins UPDATE USER + +ACL ALLOW-LOG webadmins UPDATE METHOD + +# authorise operations changing broker model +ACL ALLOW-LOG webadmins CONFIGURE BROKER + +# authorise operations to view and download broker logs +ACL ALLOW webadmins ACCESS_LOGS BROKER + +# at the moment only the following UPDATE METHOD rules are supported by web management console +#ACL ALLOW-LOG webadmins UPDATE METHOD component="VirtualHost.Queue" name="moveMessages" +#ACL ALLOW-LOG webadmins UPDATE METHOD component="VirtualHost.Queue" name="copyMessages" +#ACL ALLOW-LOG webadmins UPDATE METHOD component="VirtualHost.Queue" name="deleteMessages" + +### MESSAGING ### +# The 'ACCESS VIRTUALHOST' rules below apply to messaging operations (as opposed to management operations) + +# Firewall examples + +# Deny access to all users from *.example.company1.com and *.example.company2.com +ACL DENY-LOG all ACCESS VIRTUALHOST from_hostname=".*\.example\.company1.com,.*\.example\.company2.com" + +# Deny access to all users in the IP ranges 192.168.1.0-192.168.1.255 and 192.168.2.0-192.168.2.255, +# using the notation specified in RFC 4632, "Classless Inter-domain Routing (CIDR)" +ACL DENY-LOG messaging-users ACCESS VIRTUALHOST from_network="192.168.1.0/24,192.168.2.0/24" + +# Deny access to all users in the IP ranges 192.169.1.0-192.169.1.255 and 192.169.2.0-192.169.2.255, +# using wildcard notation. +ACL DENY-LOG messaging-users ACCESS VIRTUALHOST from_network="192.169.1.*,192.169.2.*" + +# Allow 'messaging-users' group to connect to all virtualhosts +ACL ALLOW-LOG messaging-users ACCESS VIRTUALHOST + +# Deny messaging-users management +ACL DENY-LOG messaging-users ACCESS MANAGEMENT + + +# Client side +# Allow the 'client' user to publish requests to the request queue and create, consume from, and delete temporary reply queues. +ACL ALLOW-LOG client CREATE QUEUE temporary="true" +ACL ALLOW-LOG client CONSUME QUEUE temporary="true" +ACL ALLOW-LOG client DELETE QUEUE temporary="true" +ACL ALLOW-LOG client BIND EXCHANGE name="amq.direct" temporary="true" +ACL ALLOW-LOG client UNBIND EXCHANGE name="amq.direct" temporary="true" +ACL ALLOW-LOG client PUBLISH EXCHANGE name="amq.direct" routingKey="example.RequestQueue" + +# Server side +# Allow the 'server' user to create and consume from the request queue and publish a response to the temporary response queue created by +# client. +ACL ALLOW-LOG server CREATE QUEUE name="example.RequestQueue" +ACL ALLOW-LOG server CONSUME QUEUE name="example.RequestQueue" +ACL ALLOW-LOG server BIND EXCHANGE +ACL ALLOW-LOG server PUBLISH EXCHANGE name="amq.direct" routingKey="TempQueue*" + + +### DEFAULT ### + +# Deny all users from performing all operations +ACL DENY-LOG all all diff --git a/qpid/java/broker-core/etc/groups b/qpid/java/broker-core/etc/groups new file mode 100644 index 0000000000..e3912ece99 --- /dev/null +++ b/qpid/java/broker-core/etc/groups @@ -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. +# + +# +# To define a group, use the format: +# +# .users=,,..., +# + +messaging-users.users=guest,client,server +administrators.users=admin +webadmins.users=webadmin + diff --git a/qpid/java/broker-core/etc/log4j.xml b/qpid/java/broker-core/etc/log4j.xml new file mode 100644 index 0000000000..71a13875a1 --- /dev/null +++ b/qpid/java/broker-core/etc/log4j.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qpid/java/broker-core/etc/md5passwd b/qpid/java/broker-core/etc/md5passwd new file mode 100644 index 0000000000..f7185c0e92 --- /dev/null +++ b/qpid/java/broker-core/etc/md5passwd @@ -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== +client:CE4DQ6BIb/BVMN9scFyLtA== +server:CE4DQ6BIb/BVMN9scFyLtA== +admin:ISMvKXpXpadDiUoOSoAfww== +webadmin:rda7WOE5vhAzJNBNgtj1RQ== diff --git a/qpid/java/broker-core/etc/passwd b/qpid/java/broker-core/etc/passwd new file mode 100644 index 0000000000..f0dcb80f25 --- /dev/null +++ b/qpid/java/broker-core/etc/passwd @@ -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. +# +guest:guest +client:guest +server:guest +admin:admin + +webadmin:webadmin + diff --git a/qpid/java/broker-core/python-test.xml b/qpid/java/broker-core/python-test.xml new file mode 100755 index 0000000000..5c263e3169 --- /dev/null +++ b/qpid/java/broker-core/python-test.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qpid/java/broker-core/scripts/resetAlerting.sh b/qpid/java/broker-core/scripts/resetAlerting.sh new file mode 100644 index 0000000000..18e8c64cb0 --- /dev/null +++ b/qpid/java/broker-core/scripts/resetAlerting.sh @@ -0,0 +1,116 @@ +#!/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. +# +# + +# +# Alerting Rest Scripts to renabled the alerts on the queue. +# +# Defaults to Localhost broker +# + +if [ -z "$QPID_ALERT_HOME" ]; then + export QPID_ALERT_HOME=$(dirname $(dirname $(readlink -f $0))) + export PATH=${PATH}:${QPID_ALERT_HOME}/bin +fi + +USERNAME=$1 +PASSWORD=$2 +HOSTNAME=$3 +PORT=$4 + +CLI="$QPID_ALERT_HOME/bin/qpid-cli -h ${HOSTNAME:-localhost} -p ${PORT:-8999}" +AUTH= +if [ -n $USERNAME ] ; then + if [ "$USERNAME" == "-h" ] ; then + echo "resetAlerting.sh: [ [ []]]" + exit 0 + fi + if [ -n $PASSWORD ] ; then + AUTH="-u $USERNAME -w $PASSWORD" + else + echo "Password must be specified with username" + fi +fi + + +OUTPUT=0 + +runCommand() +{ + RET=`$CLI $1 $AUTH` +} + +resetQueue() +{ + vhost=$1 + queue=$2 + runCommand "get -o queue -v $vhost -n $queue -a MaximumQueueDepth" + rawQDepth=$RET + # Note that MaxQueueDepth is returned as Kb but set as b! + queueDepth=$[ $rawQDepth * 1024 ] + runCommand "get -o queue -v $vhost -n $queue -a MaximumMessageAge" + messageAge=$RET + runCommand "get -o queue -v $vhost -n $queue -a MaximumMessageCount" + messageCount=$RET + runCommand "get -o queue -v $vhost -n $queue -a MaximumMessageSize" + messageSize=$RET + + if [ $OUTPUT == 1 ] ; then + echo Current Values: + echo MaximumQueueDepth : $queueDepth + echo MaximumMessageAge : $messageAge + echo MaximumMessageCount : $messageCount + echo MaximumMessageSize : $messageSize + fi + + runCommand "set -o queue -v $vhost -n $queue -a MaximumMessageSize -s $messageSize" + runCommand "set -o queue -v $vhost -n $queue -a MaximumMessageAge -s $messageAge" + runCommand "set -o queue -v $vhost -n $queue -a MaximumMessageCount -s $messageCount" + runCommand "set -o queue -v $vhost -n $queue -a MaximumQueueDepth -s $queueDepth" +} + +resetVirtualHost() +{ + vhost=$1 + ignore=0 + for queue in `$CLI list -o queue -v $vhost $AUTH |grep '|' | cut -d '|' -f 1 ` ; do + + if [ $ignore == 0 ] ; then + ignore=1 + else + resetQueue $vhost $queue + fi + + done +} + +VHOST=`$CLI list -o virtualhost $AUTH` +COUNT=`echo $VHOST | grep -c VirtualHost` +if [ $COUNT -gt 0 ] ; then + for vhost in `echo $VHOST |grep VirtualHost|cut -d '=' -f 3` ; do + + echo "Resetting alert levels for $vhost"; + resetVirtualHost $vhost; + done + echo "Alerting levels reset" +else + echo $VHOST +fi diff --git a/qpid/java/broker-core/src/main/java/broker.bnd b/qpid/java/broker-core/src/main/java/broker.bnd new file mode 100755 index 0000000000..8296764126 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/broker.bnd @@ -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. +# + +ver: 0.25.0 + +Bundle-SymbolicName: qpid-broker +Bundle-Version: ${ver} +Export-Package: *;version=${ver} +Bundle-RequiredExecutionEnvironment: J2SE-1.5 + diff --git a/qpid/java/broker-core/src/main/java/fallback-log4j.properties b/qpid/java/broker-core/src/main/java/fallback-log4j.properties new file mode 100644 index 0000000000..7b95a89924 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/fallback-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-core/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java b/qpid/java/broker-core/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java new file mode 100644 index 0000000000..54ca574871 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java @@ -0,0 +1,1206 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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 org.apache.log4j.helpers.CountingQuietWriter; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.LoggingEvent; + +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; + +/** + *

CompositeRollingAppender combines RollingFileAppender and DailyRollingFileAppender
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 + * rollingStyle.

To use CompositeRollingAppender to roll log files as they reach a certain size + * (like RollingFileAppender), set rollingStyle=1 (@see config.size)
To use CompositeRollingAppender to roll log + * files at certain time intervals (daily for example), set rollingStyle=2 and a datePattern (@see config.time)
To + * have CompositeRollingAppender roll log files at a certain size AND rename those according to time intervals, set + * rollingStyle=3 (@see config.composite)
+ * + *

A of few additional optional features have been added:
-- Attach date pattern for current log file (@see + * staticLogFileName)
-- Backup number increments for newer files (@see countDirection)
-- Infinite number of + * backups by file size (@see maxSizeRollBackups)

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.

A maximum number of backups based on + * date/time boundries would be nice but is not yet implemented.
+ * + * @author Kevin Steppe + * @author Heinz Richter + * @author Eirik Lygre + * @author Ceki Gülcü + * @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 */ + private Date now = new Date(); + + private SimpleDateFormat sdf; + + /** Helper class to determine next rollover time */ + private RollingCalendar rc = new RollingCalendar(); + + private long maxFileSize = 10 * 1024 * 1024; + + private int maxSizeRollBackups = 0; + private int curSizeRollBackups = 0; + + private int maxTimeRollBackups = -1; + private int curTimeRollBackups = 0; + + private int countDirection = -1; + + private int rollingStyle = BY_COMPOSITE; + private boolean rollDate = true; + private boolean rollSize = true; + + private boolean staticLogFileName = true; + + private String baseFileName; + + private boolean compress = false; + + private boolean compressAsync = false; + + private boolean zeroBased = false; + + private String backupFilesToPath = null; + private final ConcurrentLinkedQueue _compress = new ConcurrentLinkedQueue(); + private AtomicBoolean _compressing = new AtomicBoolean(false); + private static final String COMPRESS_EXTENSION = ".gz"; + + /** The default constructor does nothing. */ + public QpidCompositeRollingAppender() + { } + + /** + * Instantiate a CompositeRollingAppender and open the file designated by filename. 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 filename. The opened filename + * will become the ouput destination for this appender. + * + *

If the append parameter is true, the file will be appended to. Otherwise, the file desginated by + * filename 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 filename. 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 filename. The opened filename + * will become the output destination for this appender. + * + *

The file will be appended to. DatePattern is default. + */ + public QpidCompositeRollingAppender(Layout layout, String filename) throws IOException + { + super(layout, filename); + } + + /** + * The DatePattern 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 DatePattern option. */ + public String getDatePattern() + { + return datePattern; + } + + /** There is zero backup files by default. */ /** Returns the value of the maxSizeRollBackups 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; + } + + /** + *

Set the maximum number of backup files to keep around based on file size. + * + *

The MaxSizeRollBackups 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 MaxFileSize. 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. + * + *

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. + * + *

This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter + * taking a long argument from the setter taking a String 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. + * + *

This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter + * taking a long argument from the setter taking a String 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. + * + *

In configuration files, the MaxFileSize 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); + 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 staticLogFileName 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); + } + + super.setFile(fileName, append, bufferedIO, bufferSize); + + if (append) + { + File f = new File(fileName); + ((CountingQuietWriter) qw).setCount(f.length()); + } + } + + /** + * 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. + */ + public int getCountDirection() + { + return countDirection; + } + + public void setCountDirection(int direction) + { + countDirection = direction; + } + + /** Style of rolling to Use. BY_SIZE (1), BY_DATE(2), BY COMPOSITE(3) */ + 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 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; + } + + /** Path provided in configuration. Used for moving backup files to */ + 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 activateOptions. The following is done:
+ *
A) determine curSizeRollBackups
B) determine curTimeRollBackups (not implemented)
C) initiates a + * roll over if needed for crossing a date boundary since the last run. + */ + protected void existingInit() + { + curTimeRollBackups = 0; + + // part A starts here + // This is now down at first log when curSizeRollBackup==0 see rollFile + // 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 + * existingInit 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(); + 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. + + + rollFile(); + + try + { + 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 from to file to. 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.error("Attempting to compress file with same output name."); + } + + return; + } + + if (backupFilesToPath != null) + { + to = backupFilesToPath + System.getProperty("file.separator") + new File(to).getName(); + } + + File target = new File(to); + + File file = new File(from); + // Perform Roll by renaming + if (!file.getPath().equals(target.getPath())) + { + file.renameTo(target); + } + + // Compress file after it has been moved out the way... this is safe + // as it will gain a .gz ending and we can then safely delete this file + // as it will not be the statically named value. + if (compress) + { + compress(target); + } + + LogLog.debug(from + " -> " + to); + } + + private void compress(File target) + { + if (compressAsync) + { + synchronized (_compress) + { + _compress.offer(new CompressJob(target, target)); + } + + startCompression(); + } + else + { + doCompress(target, target); + } + } + + private void startCompression() + { + if (_compressing.compareAndSet(false, true)) + { + executor.execute(compressor); + } + } + + /** + * Delete the given file that is prepended with the relative path to the log + * directory. + * + * Compress is enabled check for file with COMPRESS_EXTENSION(.gz) + * + * if backupFilesToPath is set then check in this directory not the + * main log directory. + */ + protected void deleteFile(String relativeFileName) + { + String fileName=""; + // If we have configured a backup location then we should look in there + // for the file we are trying to delete + if (backupFilesToPath != null) + { + File file = new File(relativeFileName); + + fileName = backupFilesToPath + System.getProperty("file.separator") + file.getName(); + } + + // If we are compressing the at the extension + if (compress) + { + fileName += COMPRESS_EXTENSION; + } + + + File file = new File(fileName); + + if (file.exists()) + { + file.delete(); + } + } + + /** + * Implements roll overs base on file size. + * + *

If the maximum number of size based backups is reached (curSizeRollBackups == maxSizeRollBackups If + * countDirection < 0, then files {File.1, ..., File.curSizeRollBackups -1} + * are renamed to {File.2, ..., File.curSizeRollBackups}. Moreover, File is + * renamed File.1 and closed.
+ * + * A new file is created to receive further log output. + * + *

If maxSizeRollBackups is equal to zero, then the File is truncated with no backup + * files created. + * + *

If maxSizeRollBackups < 0, then File 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) + { + rollFile(); + } + + 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); + } + } + + /** + * Perform file Rollover ensuring the countDirection is applied along with + * the other options + */ + private void rollFile() + { + LogLog.debug("CD="+countDirection+",start"); + if (countDirection < 0) + { + // If we haven't rolled yet then validate we have the right value + // for curSizeRollBackups + if (curSizeRollBackups == 0) + { + //Validate curSizeRollBackups + curSizeRollBackups = countFileIndex(fileName); + // decrement to offset the later increment + curSizeRollBackups--; + } + + // If we are not keeping an infinite set of backups the delete oldest + if (maxSizeRollBackups > 0) + { + LogLog.debug("CD=-1,curSizeRollBackups:"+curSizeRollBackups); + LogLog.debug("CD=-1,maxSizeRollBackups:"+maxSizeRollBackups); + + // Delete the oldest file. + // curSizeRollBackups is never -1 so infinite backups are ok here + if ((curSizeRollBackups - maxSizeRollBackups) >= 0) + { + //The oldest file is the one with the largest number + // as the 0 is always fileName + // which moves to fileName.1 etc. + LogLog.debug("CD=-1,deleteFile:"+curSizeRollBackups); + deleteFile(fileName + '.' + curSizeRollBackups); + // decrement to offset the later increment + curSizeRollBackups--; + } + } + /* + map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2}. + */ + for (int i = curSizeRollBackups; i >= 1; i--) + { + String oldName = (fileName + "." + i); + String newName = (fileName + '.' + (i + 1)); + + // Ensure that when compressing we rename the compressed archives + if (compress) + { + rollFile(oldName + COMPRESS_EXTENSION, newName + COMPRESS_EXTENSION, false); + } + else + { + rollFile(oldName, newName, 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 + now.setTime(System.currentTimeMillis()); + String newFile = fileName + sdf.format(now); + + // If we haven't rolled yet then validate we have the right value + // for curSizeRollBackups + if (curSizeRollBackups == 0) + { + //Validate curSizeRollBackups + curSizeRollBackups = countFileIndex(newFile); + // to balance the increment just coming up. as the count returns + // the next free number not the last used. + curSizeRollBackups--; + } + + // If we are not keeping an infinite set of backups the delete oldest + if (maxSizeRollBackups > 0) + { + // Don't prune older files if they exist just go for the last + // one based on our maxSizeRollBackups. This means we may have + // more files left on disk that maxSizeRollBackups if this value + // is adjusted between runs but that is an acceptable state. + // Otherwise we would have to check on startup that we didn't + // have more than maxSizeRollBackups and prune then. + + if (((curSizeRollBackups - maxSizeRollBackups) >= 0)) + { + LogLog.debug("CD=0,curSizeRollBackups:"+curSizeRollBackups); + LogLog.debug("CD=0,maxSizeRollBackups:"+maxSizeRollBackups); + + // delete the first and keep counting up. + int oldestFileIndex = curSizeRollBackups - maxSizeRollBackups + 1; + LogLog.debug("CD=0,deleteFile:"+oldestFileIndex); + deleteFile(newFile + '.' + oldestFileIndex); + } + } + + + String finalName = newFile; + + curSizeRollBackups++; + + // Add rollSize if it is > 0 + if (curSizeRollBackups > 0 ) + { + finalName = newFile + '.' + curSizeRollBackups; + + } + + rollFile(fileName, finalName, compress); + } + else + { // countDirection > 0 + // If we haven't rolled yet then validate we have the right value + // for curSizeRollBackups + if (curSizeRollBackups == 0) + { + //Validate curSizeRollBackups + curSizeRollBackups = countFileIndex(fileName); + // to balance the increment just coming up. as the count returns + // the next free number not the last used. + curSizeRollBackups--; + } + + // If we are not keeping an infinite set of backups the delete oldest + if (maxSizeRollBackups > 0) + { + LogLog.debug("CD=1,curSizeRollBackups:"+curSizeRollBackups); + LogLog.debug("CD=1,maxSizeRollBackups:"+maxSizeRollBackups); + + // Don't prune older files if they exist just go for the last + // one based on our maxSizeRollBackups. This means we may have + // more files left on disk that maxSizeRollBackups if this value + // is adjusted between runs but that is an acceptable state. + // Otherwise we would have to check on startup that we didn't + // have more than maxSizeRollBackups and prune then. + + if (((curSizeRollBackups - maxSizeRollBackups) >= 0)) + { + // delete the first and keep counting up. + int oldestFileIndex = curSizeRollBackups - maxSizeRollBackups + 1; + LogLog.debug("CD=1,deleteFile:"+oldestFileIndex); + deleteFile(fileName + '.' + oldestFileIndex); + } + } + + + curSizeRollBackups++; + + rollFile(fileName, fileName + '.' + curSizeRollBackups, compress); + + } + LogLog.debug("CD="+countDirection+",done"); + } + + + private int countFileIndex(String fileName) + { + return countFileIndex(fileName, true); + } + /** + * Use filename as a base name and find what count number we are up to by + * looking at the files in this format: + * + * .[COMPRESS_EXTENSION] + * + * If a count value of 1 cannot be found then a directory listing is + * performed to try and identify if there is a valid value for . + * + * + * @param fileName the basefilename to use + * @param checkBackupLocation should backupFilesToPath location be checked for existing backups + * @return int the next free index + */ + private int countFileIndex(String fileName, boolean checkBackupLocation) + { + String testFileName; + + // It is possible for index 1..n to be missing leaving n+1..n+1+m logs + // in this scenario we should still return n+1+m+1 + int index=1; + + testFileName = fileName + "." + index; + + // Bail out early if there is a problem with the file + if (new File(testFileName) == null + || new File(testFileName + COMPRESS_EXTENSION) == null) + + { + return index; + } + + // Check that we do not have the 1..n missing scenario + if (!(new File(testFileName).exists() + || new File(testFileName + COMPRESS_EXTENSION).exists())) + + { + int max=0; + String prunedFileName = new File(fileName).getName(); + + // Look through all files to find next index + if (new File(fileName).getParentFile() != null) + { + for (File file : new File(fileName).getParentFile().listFiles()) + { + String name = file.getName(); + + if (name.startsWith(prunedFileName) && !name.equals(prunedFileName)) + { + String parsedCount = name.substring(prunedFileName.length() + 1); + + if (parsedCount.endsWith(COMPRESS_EXTENSION)) + { + parsedCount = parsedCount.substring(0, parsedCount.indexOf(COMPRESS_EXTENSION)); + } + + try + { + max = Integer.parseInt(parsedCount); + + // if we got a good value then update our index value. + if (max > index) + { + // +1 as we want to return the next free value. + index = max + 1; + } + } + catch (NumberFormatException nfe) + { + //ignore it assume file doesn't exist. + } + } + } + } + + // Update testFileName + testFileName = fileName + "." + index; + } + + + while (new File(testFileName).exists() + || new File(testFileName + COMPRESS_EXTENSION).exists()) + { + index++; + testFileName = fileName + "." + index; + } + + if (checkBackupLocation && index == 1 && backupFilesToPath != null) + { + LogLog.debug("Trying backup location:"+backupFilesToPath + System.getProperty("file.separator") + fileName); + return countFileIndex(backupFilesToPath + System.getProperty("file.separator") + new File(fileName).getName(), false); + } + + return index; + } + + protected synchronized void doCompress(File from, File to) + { + String toFile; + + toFile = to.getPath() + COMPRESS_EXTENSION; + + 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)); + try + { + // Open the input file + FileInputStream in = new FileInputStream(from); + try + { + // 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); + } + } + finally + { + in.close(); + } + + // Complete the GZIP file + out.finish(); + } + finally + { + out.close(); + } + // Remove old file. + from.delete(); + } + catch (IOException e) + { + if (target.exists()) + { + target.delete(); + } + + rollFile(from.getPath(), to.getPath(), false); + } + } + + /** The default maximum file size is 10MB. */ + protected long getMaxFileSize() + { + return maxFileSize; + } + + /** How many sized based backups have been made so far */ + protected int getCurSizeRollBackups() + { + return curSizeRollBackups; + } + + protected void setCurSizeRollBackups(int curSizeRollBackups) + { + this.curSizeRollBackups = curSizeRollBackups; + } + + /** not yet implemented */ + protected int getMaxTimeRollBackups() + { + return maxTimeRollBackups; + } + + protected void setMaxTimeRollBackups(int maxTimeRollBackups) + { + this.maxTimeRollBackups = maxTimeRollBackups; + } + + protected int getCurTimeRollBackups() + { + return curTimeRollBackups; + } + + protected void setCurTimeRollBackups(int curTimeRollBackups) + { + this.curTimeRollBackups = curTimeRollBackups; + } + + protected boolean isRollDate() + { + return rollDate; + } + + protected void setRollDate(boolean rollDate) + { + this.rollDate = rollDate; + } + + protected boolean isRollSize() + { + return rollSize; + } + + protected void setRollSize(boolean rollSize) + { + this.rollSize = rollSize; + } + + /** + * 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 isStaticLogFileName() + { + return staticLogFileName; + } + + /** FileName provided in configuration. Used for rolling properly */ + protected String getBaseFileName() + { + return baseFileName; + } + + protected void setBaseFileName(String baseFileName) + { + this.baseFileName = baseFileName; + } + + /** Do we want to .gz our backup files. */ + protected boolean isCompress() + { + return compress; + } + + protected void setCompress(boolean compress) + { + this.compress = compress; + } + + /** Do we want to use a second thread when compressing our backup files. */ + protected boolean isCompressAsync() + { + return compressAsync; + } + + /** Do we want to start numbering files at zero. */ + protected boolean isZeroBased() + { + return zeroBased; + } + + protected void setBackupFilesToPath(String backupFilesToPath) + { + this.backupFilesToPath = backupFilesToPath; + } + + private static class CompressJob + { + private File _from, _to; + + CompressJob(File from, File to) + { + _from = from; + _to = to; + } + + File getFrom() + { + return _from; + } + + File getTo() + { + return _to; + } + } + + private Compressor compressor = null; + + private 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-core/src/main/java/org/apache/qpid/server/Broker.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/Broker.java new file mode 100644 index 0000000000..7de0ebe1de --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/Broker.java @@ -0,0 +1,279 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.BrokerConfigurationStoreCreator; +import org.apache.qpid.server.configuration.store.ManagementModeStoreHandler; +import org.apache.qpid.server.logging.SystemOutMessageLogger; +import org.apache.qpid.server.logging.actors.BrokerActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.log4j.LoggingManagementFacade; +import org.apache.qpid.server.logging.messages.BrokerMessages; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; + +public class Broker +{ + private static final Logger LOGGER = Logger.getLogger(Broker.class); + + private volatile Thread _shutdownHookThread; + private volatile IApplicationRegistry _applicationRegistry; + + protected static class InitException extends RuntimeException + { + private static final long serialVersionUID = 1L; + + InitException(String msg, Throwable cause) + { + super(msg, cause); + } + } + + public void shutdown() + { + try + { + removeShutdownHook(); + } + finally + { + if (_applicationRegistry != null) + { + _applicationRegistry.close(); + } + + } + } + + public void startup() throws Exception + { + startup(new BrokerOptions()); + } + + public void startup(final BrokerOptions options) throws Exception + { + CurrentActor.set(new BrokerActor(new SystemOutMessageLogger())); + try + { + startupImpl(options); + addShutdownHook(); + } + finally + { + CurrentActor.remove(); + + } + } + + private void startupImpl(final BrokerOptions options) throws Exception + { + String storeLocation = options.getConfigurationStoreLocation(); + String storeType = options.getConfigurationStoreType(); + + CurrentActor.get().message(BrokerMessages.CONFIG(storeLocation)); + + //Allow skipping the logging configuration for people who are + //embedding the broker and want to configure it themselves. + if(!options.isSkipLoggingConfiguration()) + { + configureLogging(new File(options.getLogConfigFileLocation()), options.getLogWatchFrequency()); + } + + BrokerConfigurationStoreCreator storeCreator = new BrokerConfigurationStoreCreator(); + ConfigurationEntryStore store = storeCreator.createStore(storeLocation, storeType, options.getInitialConfigurationLocation(), + options.isOverwriteConfigurationStore(), options.getConfigProperties()); + + if (options.isManagementMode()) + { + store = new ManagementModeStoreHandler(store, options); + } + + _applicationRegistry = new ApplicationRegistry(store); + try + { + _applicationRegistry.initialise(options); + } + catch(Exception e) + { + try + { + _applicationRegistry.close(); + } + catch(Exception ce) + { + LOGGER.debug("An error occured when closing the registry following initialization failure", ce); + } + throw e; + } + + } + + public static void parsePortList(Set output, List ports) throws InitException + { + if(ports != null) + { + for(Object o : ports) + { + try + { + output.add(Integer.parseInt(String.valueOf(o))); + } + catch (NumberFormatException e) + { + throw new InitException("Invalid port: " + o, e); + } + } + } + } + + private void configureLogging(File logConfigFile, int logWatchTime) throws InitException, IOException + { + if (logConfigFile.exists() && logConfigFile.canRead()) + { + CurrentActor.get().message(BrokerMessages.LOG_CONFIG(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 + try + { + LoggingManagementFacade.configureAndWatch(logConfigFile.getPath(), logWatchTime * 1000); + } + catch (Exception e) + { + throw new InitException(e.getMessage(),e); + } + } + else + { + try + { + LoggingManagementFacade.configure(logConfigFile.getPath()); + } + catch (Exception e) + { + throw new InitException(e.getMessage(),e); + } + } + } + else + { + System.err.println("Logging configuration error: unable to read file " + logConfigFile.getAbsolutePath()); + System.err.println("Using the fallback internal fallback-log4j.properties configuration"); + + InputStream propsFile = this.getClass().getResourceAsStream("/fallback-log4j.properties"); + if(propsFile == null) + { + throw new IOException("Unable to load the fallback internal fallback-log4j.properties configuration file"); + } + else + { + try + { + Properties fallbackProps = new Properties(); + fallbackProps.load(propsFile); + PropertyConfigurator.configure(fallbackProps); + } + finally + { + propsFile.close(); + } + } + } + } + + + private void addShutdownHook() + { + Thread shutdownHookThread = new Thread(new ShutdownService()); + shutdownHookThread.setName("QpidBrokerShutdownHook"); + + Runtime.getRuntime().addShutdownHook(shutdownHookThread); + _shutdownHookThread = shutdownHookThread; + + LOGGER.debug("Added shutdown hook"); + } + + private void removeShutdownHook() + { + Thread shutdownThread = _shutdownHookThread; + + //if there is a shutdown thread and we aren't it, we should remove it + if(shutdownThread != null && !(Thread.currentThread() == shutdownThread)) + { + LOGGER.debug("Removing shutdown hook"); + + _shutdownHookThread = null; + + boolean removed = false; + try + { + removed = Runtime.getRuntime().removeShutdownHook(shutdownThread); + } + catch(IllegalStateException ise) + { + //ignore, means the JVM is already shutting down + } + + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("Removed shutdown hook: " + removed); + } + } + else + { + LOGGER.debug("Skipping shutdown hook removal as there either isnt one, or we are it."); + } + } + + public org.apache.qpid.server.model.Broker getBroker() + { + if (_applicationRegistry == null) + { + return null; + } + return _applicationRegistry.getBroker(); + } + + private class ShutdownService implements Runnable + { + public void run() + { + LOGGER.debug("Shutdown hook running"); + Broker.this.shutdown(); + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/BrokerOptions.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/BrokerOptions.java new file mode 100644 index 0000000000..d5f344f4ed --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/BrokerOptions.java @@ -0,0 +1,376 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.store.MemoryConfigurationEntryStore; +import org.apache.qpid.server.util.StringUtil; + +public class BrokerOptions +{ + /** + * Configuration property name for the absolute path to use for the broker work directory. + * + * If not otherwise set, the value for this configuration property defaults to the location + * set in the "QPID_WORK" system property if that was set, or the 'work' sub-directory of + * the JVM working directory ("user.dir" property) for the Java process if it was not. + */ + public static final String QPID_WORK_DIR = "qpid.work_dir"; + /** + * Configuration property name for the absolute path to use for the broker home directory. + * + * If not otherwise set, the value for this configuration property defaults to the location + * set in the "QPID_HOME" system property if that was set, or remains unset if it was not. + */ + public static final String QPID_HOME_DIR = "qpid.home_dir"; + public static final String QPID_AMQP_PORT = "qpid.amqp_port"; + public static final String QPID_HTTP_PORT = "qpid.http_port"; + public static final String QPID_RMI_PORT = "qpid.rmi_port"; + public static final String QPID_JMX_PORT = "qpid.jmx_port"; + + public static final String DEFAULT_AMQP_PORT_NUMBER = "5672"; + public static final String DEFAULT_HTTP_PORT_NUMBER = "8080"; + public static final String DEFAULT_RMI_PORT_NUMBER = "8999"; + public static final String DEFAULT_JMX_PORT_NUMBER = "9099"; + + public static final String DEFAULT_INITIAL_CONFIG_NAME = "initial-config.json"; + public static final String DEFAULT_STORE_TYPE = "json"; + public static final String DEFAULT_CONFIG_NAME_PREFIX = "config"; + public static final String DEFAULT_LOG_CONFIG_FILE = "etc/log4j.xml"; + public static final String DEFAULT_INITIAL_CONFIG_LOCATION = + BrokerOptions.class.getClassLoader().getResource(DEFAULT_INITIAL_CONFIG_NAME).toExternalForm(); + public static final String MANAGEMENT_MODE_USER_NAME = "mm_admin"; + private static final int MANAGEMENT_MODE_PASSWORD_LENGTH = 10; + + private static final File FALLBACK_WORK_DIR = new File(System.getProperty("user.dir"), "work"); + + private String _logConfigFile; + private Integer _logWatchFrequency = 0; + + private String _configurationStoreLocation; + private String _configurationStoreType; + + private String _initialConfigurationLocation; + + private boolean _managementMode; + private boolean _managementModeQuiesceVhosts; + private int _managementModeRmiPortOverride; + private int _managementModeJmxPortOverride; + private int _managementModeHttpPortOverride; + private String _managementModePassword; + private boolean _skipLoggingConfiguration; + private boolean _overwriteConfigurationStore; + private Map _configProperties = new HashMap(); + + public String getManagementModePassword() + { + if(_managementModePassword == null) + { + _managementModePassword = new StringUtil().randomAlphaNumericString(MANAGEMENT_MODE_PASSWORD_LENGTH); + } + + return _managementModePassword; + } + + public void setManagementModePassword(String managementModePassword) + { + _managementModePassword = managementModePassword; + } + + public int getLogWatchFrequency() + { + return _logWatchFrequency; + } + + /** + * Set the frequency with which the log config file will be checked for updates. + * @param logWatchFrequency frequency in seconds + */ + public void setLogWatchFrequency(final int logWatchFrequency) + { + _logWatchFrequency = logWatchFrequency; + } + + public boolean isManagementMode() + { + return _managementMode; + } + + public void setManagementMode(boolean managementMode) + { + _managementMode = managementMode; + } + + public boolean isManagementModeQuiesceVirtualHosts() + { + return _managementModeQuiesceVhosts; + } + + public void setManagementModeQuiesceVirtualHosts(boolean managementModeQuiesceVhosts) + { + _managementModeQuiesceVhosts = managementModeQuiesceVhosts; + } + + public int getManagementModeRmiPortOverride() + { + return _managementModeRmiPortOverride; + } + + public void setManagementModeRmiPortOverride(int managementModeRmiPortOverride) + { + _managementModeRmiPortOverride = managementModeRmiPortOverride; + } + + public int getManagementModeJmxPortOverride() + { + return _managementModeJmxPortOverride; + } + + public void setManagementModeJmxPortOverride(int managementModeJmxPortOverride) + { + _managementModeJmxPortOverride = managementModeJmxPortOverride; + } + + public int getManagementModeHttpPortOverride() + { + return _managementModeHttpPortOverride; + } + + public void setManagementModeHttpPortOverride(int managementModeHttpPortOverride) + { + _managementModeHttpPortOverride = managementModeHttpPortOverride; + } + + /** + * Get the broker configuration store type. + * + * @return the previously set store type, or if none was set the default: {@value #DEFAULT_STORE_TYPE} + */ + public String getConfigurationStoreType() + { + if(_configurationStoreType == null) + { + return DEFAULT_STORE_TYPE; + } + + return _configurationStoreType; + } + + /** + * Set the broker configuration store type. + * + * Passing null clears previously set values and returns to the default. + */ + public void setConfigurationStoreType(String cofigurationStoreType) + { + _configurationStoreType = cofigurationStoreType; + } + + /** + * Get the broker configuration store location. + * + * Defaults to {@value #DEFAULT_CONFIG_NAME_PREFIX}.{@literal } (see {@link BrokerOptions#getConfigurationStoreType()}) + * within the broker work directory (gathered via config property {@link #QPID_WORK_DIR}). + * + * @return the previously set configuration store location, or the default location if none was set. + */ + public String getConfigurationStoreLocation() + { + if(_configurationStoreLocation == null) + { + String workDir = getWorkDir(); + String storeType = getConfigurationStoreType(); + + return new File(workDir, DEFAULT_CONFIG_NAME_PREFIX + "." + storeType).getAbsolutePath(); + } + + return _configurationStoreLocation; + } + + /** + * Set the absolute path to use for the broker configuration store. + * + * Passing null clears any previously set value and returns to the default. + */ + public void setConfigurationStoreLocation(String cofigurationStore) + { + _configurationStoreLocation = cofigurationStore; + } + + /** + * Returns whether the existing broker configuration store should be overwritten with the current + * initial configuration file (see {@link BrokerOptions#getInitialConfigurationLocation()}). + */ + public boolean isOverwriteConfigurationStore() + { + return _overwriteConfigurationStore; + } + + /** + * Sets whether the existing broker configuration store should be overwritten with the current + * initial configuration file (see {@link BrokerOptions#getInitialConfigurationLocation()}). + */ + public void setOverwriteConfigurationStore(boolean overwrite) + { + _overwriteConfigurationStore = overwrite; + } + + /** + * Get the broker initial JSON configuration location. + * + * Defaults to an internal configuration file within the broker jar. + * + * @return the previously set configuration location, or the default location if none was set. + */ + public String getInitialConfigurationLocation() + { + if(_initialConfigurationLocation == null) + { + return DEFAULT_INITIAL_CONFIG_LOCATION; + } + + return _initialConfigurationLocation; + } + + /** + * Set the absolute path or URL to use for the initial JSON configuration, which is loaded with the + * {@link MemoryConfigurationEntryStore} in order to initialise any new {@link ConfigurationEntryStore} for the broker. + * + * Passing null clears any previously set value and returns to the default. + */ + public void setInitialConfigurationLocation(String initialConfigurationLocation) + { + _initialConfigurationLocation = initialConfigurationLocation; + } + + public boolean isSkipLoggingConfiguration() + { + return _skipLoggingConfiguration; + } + + public void setSkipLoggingConfiguration(boolean skipLoggingConfiguration) + { + _skipLoggingConfiguration = skipLoggingConfiguration; + } + + /** + * Sets the named configuration property to the given value. + * + * Passing a null value causes removal of a previous value, and restores any default there may have been. + */ + public void setConfigProperty(String name, String value) + { + if(value == null) + { + _configProperties.remove(name); + } + else + { + _configProperties.put(name, value); + } + } + + /** + * Get an un-editable copy of the configuration properties, representing + * the user-configured values as well as any defaults for properties + * not otherwise configured. + * + * Subsequent property changes are not reflected in this map. + */ + public Map getConfigProperties() + { + ConcurrentHashMap properties = new ConcurrentHashMap(); + properties.putAll(_configProperties); + + properties.putIfAbsent(QPID_AMQP_PORT, String.valueOf(DEFAULT_AMQP_PORT_NUMBER)); + properties.putIfAbsent(QPID_HTTP_PORT, String.valueOf(DEFAULT_HTTP_PORT_NUMBER)); + properties.putIfAbsent(QPID_RMI_PORT, String.valueOf(DEFAULT_RMI_PORT_NUMBER)); + properties.putIfAbsent(QPID_JMX_PORT, String.valueOf(DEFAULT_JMX_PORT_NUMBER)); + properties.putIfAbsent(QPID_WORK_DIR, getWorkDir()); + + String homeDir = getHomeDir(); + if(homeDir != null) + { + properties.putIfAbsent(QPID_HOME_DIR, homeDir); + } + + return Collections.unmodifiableMap(properties); + } + + /** + * Get the broker logging configuration file location. + * + * If not previously explicitly set, defaults to {@value #DEFAULT_LOG_CONFIG_FILE} within the broker + * home directory if configured (gathered via config property {@link #QPID_HOME_DIR}) or the current + * JVM working directory if not. + * + * @return the previously set logging configuration file location, or the default location if none was set. + */ + public String getLogConfigFileLocation() + { + if(_logConfigFile == null) + { + String homeDir = getHomeDir(); + + return new File(homeDir, DEFAULT_LOG_CONFIG_FILE).getAbsolutePath(); + } + + return _logConfigFile; + } + + public void setLogConfigFileLocation(final String logConfigFile) + { + _logConfigFile = logConfigFile; + } + + private String getWorkDir() + { + if(!_configProperties.containsKey(QPID_WORK_DIR)) + { + String qpidWork = System.getProperty(BrokerProperties.PROPERTY_QPID_WORK); + if (qpidWork == null) + { + return FALLBACK_WORK_DIR.getAbsolutePath(); + } + + return qpidWork; + } + + return _configProperties.get(QPID_WORK_DIR); + } + + private String getHomeDir() + { + if(!_configProperties.containsKey(QPID_HOME_DIR)) + { + return System.getProperty(BrokerProperties.PROPERTY_QPID_HOME); + } + + return _configProperties.get(QPID_HOME_DIR); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/Main.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/Main.java new file mode 100644 index 0000000000..20b73e965c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/Main.java @@ -0,0 +1,381 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server; + +import java.io.File; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.PosixParser; +import org.apache.log4j.Logger; +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.server.configuration.store.ConfigurationEntryStoreUtil; + +/** + * Main entry point for AMQPD. + * + */ +public class Main +{ + private static final Option OPTION_HELP = new Option("h", "help", false, "print this message"); + + private static final Option OPTION_VERSION = new Option("v", "version", false, "print the version information and exit"); + + private static final Option OPTION_CONFIGURATION_STORE_PATH = OptionBuilder.withArgName("path").hasArg() + .withDescription("use given configuration store location").withLongOpt("store-path").create("sp"); + + private static final Option OPTION_CONFIGURATION_STORE_TYPE = OptionBuilder.withArgName("type").hasArg() + .withDescription("use given broker configuration store type").withLongOpt("store-type").create("st"); + + private static final Option OPTION_INITIAL_CONFIGURATION_PATH = OptionBuilder.withArgName("path").hasArg() + .withDescription("set the location of initial JSON config to use when creating/overwriting a broker configuration store").withLongOpt("initial-config-path").create("icp"); + + private static final Option OPTION_OVERWRITE_CONFIGURATION_STORE = OptionBuilder.withDescription("overwrite the broker configuration store with the current initial configuration") + .withLongOpt("overwrite-store").create("os"); + + private static final Option OPTION_CREATE_INITIAL_CONFIG = OptionBuilder.withArgName("path").hasOptionalArg().withDescription("create a copy of the initial config file, either to an" + + " optionally specified file path, or as " + BrokerOptions.DEFAULT_INITIAL_CONFIG_NAME + " in the current directory") + .withLongOpt("create-initial-config").create("cic"); + + private static final Option OPTION_CONFIGURATION_PROPERTY = OptionBuilder.withArgName("name=value").hasArg() + .withDescription("set a configuration property to use when resolving variables in the broker configuration store, with format \"name=value\"") + .withLongOpt("config-property").create("prop"); + + private static final Option OPTION_LOG_CONFIG_FILE = + OptionBuilder.withArgName("file").hasArg() + .withDescription("use the specified log4j xml configuration file. By " + + "default looks for a file named " + BrokerOptions.DEFAULT_LOG_CONFIG_FILE + + " in the same directory as the configuration file").withLongOpt("logconfig").create("l"); + + private static final Option OPTION_LOG_WATCH = + OptionBuilder.withArgName("period").hasArg() + .withDescription("monitor the log file configuration file for changes. Units are seconds. " + + "Zero means do not check for changes.").withLongOpt("logwatch").create("w"); + + private static final Option OPTION_MANAGEMENT_MODE = OptionBuilder.withDescription("start broker in management mode, disabling the AMQP ports") + .withLongOpt("management-mode").create("mm"); + private static final Option OPTION_MM_QUIESCE_VHOST = OptionBuilder.withDescription("make virtualhosts stay in the quiesced state during management mode.") + .withLongOpt("management-mode-quiesce-virtualhosts").create("mmqv"); + private static final Option OPTION_MM_RMI_PORT = OptionBuilder.withArgName("port").hasArg() + .withDescription("override jmx rmi registry port in management mode").withLongOpt("management-mode-rmi-registry-port").create("mmrmi"); + private static final Option OPTION_MM_CONNECTOR_PORT = OptionBuilder.withArgName("port").hasArg() + .withDescription("override jmx connector port in management mode").withLongOpt("management-mode-jmx-connector-port").create("mmjmx"); + private static final Option OPTION_MM_HTTP_PORT = OptionBuilder.withArgName("port").hasArg() + .withDescription("override http management port in management mode").withLongOpt("management-mode-http-port").create("mmhttp"); + private static final Option OPTION_MM_PASSWORD = OptionBuilder.withArgName("password").hasArg() + .withDescription("Set the password for the management mode user " + BrokerOptions.MANAGEMENT_MODE_USER_NAME).withLongOpt("management-mode-password").create("mmpass"); + + private static final Options OPTIONS = new Options(); + + static + { + OPTIONS.addOption(OPTION_HELP); + OPTIONS.addOption(OPTION_VERSION); + OPTIONS.addOption(OPTION_CONFIGURATION_STORE_PATH); + OPTIONS.addOption(OPTION_CONFIGURATION_STORE_TYPE); + OPTIONS.addOption(OPTION_OVERWRITE_CONFIGURATION_STORE); + OPTIONS.addOption(OPTION_CREATE_INITIAL_CONFIG); + OPTIONS.addOption(OPTION_LOG_CONFIG_FILE); + OPTIONS.addOption(OPTION_LOG_WATCH); + OPTIONS.addOption(OPTION_INITIAL_CONFIGURATION_PATH); + OPTIONS.addOption(OPTION_MANAGEMENT_MODE); + OPTIONS.addOption(OPTION_MM_QUIESCE_VHOST); + OPTIONS.addOption(OPTION_MM_RMI_PORT); + OPTIONS.addOption(OPTION_MM_CONNECTOR_PORT); + OPTIONS.addOption(OPTION_MM_HTTP_PORT); + OPTIONS.addOption(OPTION_MM_PASSWORD); + OPTIONS.addOption(OPTION_CONFIGURATION_PROPERTY); + } + + protected CommandLine _commandLine; + + public static void main(String[] args) + { + //if the -Dlog4j.configuration property has not been set, enable the init override + //to stop Log4J wondering off and picking up the first log4j.xml/properties file it + //finds from the classpath when we get the first Loggers + if(System.getProperty("log4j.configuration") == null) + { + System.setProperty("log4j.defaultInitOverride", "true"); + } + + new Main(args); + } + + public Main(final String[] args) + { + if (parseCommandline(args)) + { + try + { + execute(); + } + catch(Throwable e) + { + System.err.println("Exception during startup: " + e); + e.printStackTrace(); + shutdown(1); + } + } + } + + protected boolean parseCommandline(final 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 execute() throws Exception + { + BrokerOptions options = new BrokerOptions(); + String initialConfigLocation = _commandLine.getOptionValue(OPTION_INITIAL_CONFIGURATION_PATH.getOpt()); + if (initialConfigLocation != null) + { + options.setInitialConfigurationLocation(initialConfigLocation); + } + + //process the remaining options + if (_commandLine.hasOption(OPTION_HELP.getOpt())) + { + final HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Qpid", OPTIONS, true); + } + else if (_commandLine.hasOption(OPTION_CREATE_INITIAL_CONFIG.getOpt())) + { + File destinationFile = null; + + String destinationOption = _commandLine.getOptionValue(OPTION_CREATE_INITIAL_CONFIG.getOpt()); + if (destinationOption != null) + { + destinationFile = new File(destinationOption); + } + else + { + destinationFile = new File(System.getProperty("user.dir"), BrokerOptions.DEFAULT_INITIAL_CONFIG_NAME); + } + + ConfigurationEntryStoreUtil util = new ConfigurationEntryStoreUtil(); + util.copyInitialConfigFile(options.getInitialConfigurationLocation(), destinationFile); + + System.out.println("Initial config written to: " + destinationFile.getAbsolutePath()); + } + else if (_commandLine.hasOption(OPTION_VERSION.getOpt())) + { + final StringBuilder protocol = new StringBuilder("AMQP version(s) [major.minor]: "); + boolean first = true; + for (final ProtocolVersion pv : ProtocolVersion.getSupportedProtocolVersions()) + { + if (first) + { + first = false; + } + else + { + protocol.append(", "); + } + + protocol.append(pv.getMajorVersion()).append('-').append(pv.getMinorVersion()); + } + System.out.println(QpidProperties.getVersionString() + " (" + protocol + ")"); + } + else + { + String[] configPropPairs = _commandLine.getOptionValues(OPTION_CONFIGURATION_PROPERTY.getOpt()); + if(configPropPairs != null && configPropPairs.length > 0) + { + for(String s : configPropPairs) + { + int firstEquals = s.indexOf("="); + if(firstEquals == -1) + { + throw new IllegalArgumentException("Configuration property argument is not of the format name=value: " + s); + } + String name = s.substring(0, firstEquals); + String value = s.substring(firstEquals + 1); + + if(name.equals("")) + { + throw new IllegalArgumentException("Configuration property argument is not of the format name=value: " + s); + } + + options.setConfigProperty(name, value); + } + } + + String configurationStore = _commandLine.getOptionValue(OPTION_CONFIGURATION_STORE_PATH.getOpt()); + if (configurationStore != null) + { + options.setConfigurationStoreLocation(configurationStore); + } + + String configurationStoreType = _commandLine.getOptionValue(OPTION_CONFIGURATION_STORE_TYPE.getOpt()); + if (configurationStoreType != null) + { + options.setConfigurationStoreType(configurationStoreType); + } + + String logWatchConfig = _commandLine.getOptionValue(OPTION_LOG_WATCH.getOpt()); + if(logWatchConfig != null) + { + options.setLogWatchFrequency(Integer.parseInt(logWatchConfig)); + } + + String logConfig = _commandLine.getOptionValue(OPTION_LOG_CONFIG_FILE.getOpt()); + if(logConfig != null) + { + options.setLogConfigFileLocation(logConfig); + } + + boolean overwriteConfigurationStore = _commandLine.hasOption(OPTION_OVERWRITE_CONFIGURATION_STORE.getOpt()); + options.setOverwriteConfigurationStore(overwriteConfigurationStore); + + boolean managementMode = _commandLine.hasOption(OPTION_MANAGEMENT_MODE.getOpt()); + if (managementMode) + { + options.setManagementMode(true); + String rmiPort = _commandLine.getOptionValue(OPTION_MM_RMI_PORT.getOpt()); + if (rmiPort != null) + { + options.setManagementModeRmiPortOverride(Integer.parseInt(rmiPort)); + } + String connectorPort = _commandLine.getOptionValue(OPTION_MM_CONNECTOR_PORT.getOpt()); + if (connectorPort != null) + { + options.setManagementModeJmxPortOverride(Integer.parseInt(connectorPort)); + } + String httpPort = _commandLine.getOptionValue(OPTION_MM_HTTP_PORT.getOpt()); + if (httpPort != null) + { + options.setManagementModeHttpPortOverride(Integer.parseInt(httpPort)); + } + + boolean quiesceVhosts = _commandLine.hasOption(OPTION_MM_QUIESCE_VHOST.getOpt()); + options.setManagementModeQuiesceVirtualHosts(quiesceVhosts); + + String password = _commandLine.getOptionValue(OPTION_MM_PASSWORD.getOpt()); + if (password != null) + { + options.setManagementModePassword(password); + } + } + setExceptionHandler(); + + startBroker(options); + } + } + + protected void setExceptionHandler() + { + Thread.UncaughtExceptionHandler handler = null; + String handlerClass = System.getProperty("qpid.broker.exceptionHandler"); + if(handlerClass != null) + { + try + { + handler = (Thread.UncaughtExceptionHandler) Class.forName(handlerClass).newInstance(); + } + catch (ClassNotFoundException e) + { + + } + catch (InstantiationException e) + { + + } + catch (IllegalAccessException e) + { + + } + catch (ClassCastException e) + { + + } + } + + if(handler == null) + { + handler = + new Thread.UncaughtExceptionHandler() + { + public void uncaughtException(final Thread t, final Throwable e) + { + boolean continueOnError = Boolean.getBoolean("qpid.broker.exceptionHandler.continue"); + try + { + System.err.println("########################################################################"); + System.err.println("#"); + System.err.print("# Unhandled Exception "); + System.err.print(e.toString()); + System.err.print(" in Thread "); + System.err.println(t.getName()); + System.err.println("#"); + System.err.println(continueOnError ? "# Forced to continue by JVM setting 'qpid.broker.exceptionHandler.continue'" : "# Exiting"); + System.err.println("#"); + System.err.println("########################################################################"); + e.printStackTrace(System.err); + + Logger logger = Logger.getLogger("org.apache.qpid.server.Main"); + logger.error("Uncaught exception, " + (continueOnError ? "continuing." : "shutting down."), e); + } + finally + { + if (!continueOnError) + { + Runtime.getRuntime().halt(1); + } + } + + } + }; + + Thread.setDefaultUncaughtExceptionHandler(handler); + } + } + + protected void startBroker(final BrokerOptions options) throws Exception + { + Broker broker = new Broker(); + broker.startup(options); + } + + protected void shutdown(final int status) + { + System.exit(status); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/TransactionTimeoutHelper.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/TransactionTimeoutHelper.java new file mode 100644 index 0000000000..b7007bf768 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/TransactionTimeoutHelper.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; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ChannelMessages; +import org.apache.qpid.server.txn.ServerTransaction; + +public class TransactionTimeoutHelper +{ + private static final String OPEN_TRANSACTION_TIMEOUT_ERROR = "Open transaction timed out"; + private static final String IDLE_TRANSACTION_TIMEOUT_ERROR = "Idle transaction timed out"; + + private final LogSubject _logSubject; + + private final CloseAction _closeAction; + + public TransactionTimeoutHelper(final LogSubject logSubject, final CloseAction closeAction) + { + _logSubject = logSubject; + _closeAction = closeAction; + } + + public void checkIdleOrOpenTimes(ServerTransaction transaction, long openWarn, long openClose, long idleWarn, long idleClose) throws AMQException + { + if (transaction.isTransactional()) + { + final long transactionUpdateTime = transaction.getTransactionUpdateTime(); + if(transactionUpdateTime > 0) + { + long idleTime = System.currentTimeMillis() - transactionUpdateTime; + boolean closed = logAndCloseIfNecessary(idleTime, idleWarn, idleClose, ChannelMessages.IDLE_TXN(idleTime), IDLE_TRANSACTION_TIMEOUT_ERROR); + if (closed) + { + return; // no point proceeding to check the open time + } + } + + final long transactionStartTime = transaction.getTransactionStartTime(); + if(transactionStartTime > 0) + { + long openTime = System.currentTimeMillis() - transactionStartTime; + logAndCloseIfNecessary(openTime, openWarn, openClose, ChannelMessages.OPEN_TXN(openTime), OPEN_TRANSACTION_TIMEOUT_ERROR); + } + } + } + + /** + * @return true iff closeTimeout was exceeded + */ + private boolean logAndCloseIfNecessary(final long timeSoFar, + final long warnTimeout, final long closeTimeout, + final LogMessage warnMessage, final String closeMessage) throws AMQException + { + if (isTimedOut(timeSoFar, warnTimeout)) + { + LogActor logActor = CurrentActor.get(); + logActor.message(_logSubject, warnMessage); + } + + if(isTimedOut(timeSoFar, closeTimeout)) + { + _closeAction.doTimeoutAction(closeMessage); + return true; + } + else + { + return false; + } + } + + private boolean isTimedOut(long timeSoFar, long timeout) + { + return timeout > 0L && timeSoFar > timeout; + } + + public interface CloseAction + { + void doTimeoutAction(String reason) throws AMQException; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/binding/Binding.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/binding/Binding.java new file mode 100644 index 0000000000..469a4bb9d0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/binding/Binding.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.binding; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; + +import java.util.Collections; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; + +public class Binding +{ + private final String _bindingKey; + private final AMQQueue _queue; + private final Exchange _exchange; + private final Map _arguments; + private final UUID _id; + private final AtomicLong _matches = new AtomicLong(); + + public Binding(UUID id, + final String bindingKey, + final AMQQueue queue, + final Exchange exchange, + final Map arguments) + { + _id = id; + _bindingKey = bindingKey; + _queue = queue; + _exchange = exchange; + _arguments = arguments == null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(arguments); + } + + public UUID getId() + { + return _id; + } + + public String getBindingKey() + { + return _bindingKey; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public Exchange getExchange() + { + return _exchange; + } + + public Map getArguments() + { + return _arguments; + } + + public void incrementMatches() + { + _matches.incrementAndGet(); + } + + public long getMatches() + { + return _matches.get(); + } + + boolean isDurable() + { + return _queue.isDurable() && _exchange.isDurable(); + } + + @Override + public boolean equals(final Object o) + { + if (this == o) + { + return true; + } + + if (!(o instanceof Binding)) + { + return false; + } + + final Binding binding = (Binding) o; + + return (_bindingKey == null ? binding.getBindingKey() == null : _bindingKey.equals(binding.getBindingKey())) + && (_exchange == null ? binding.getExchange() == null : _exchange.equals(binding.getExchange())) + && (_queue == null ? binding.getQueue() == null : _queue.equals(binding.getQueue())); + } + + @Override + public int hashCode() + { + int result = _bindingKey == null ? 1 : _bindingKey.hashCode(); + result = 31 * result + (_queue == null ? 3 : _queue.hashCode()); + result = 31 * result + (_exchange == null ? 5 : _exchange.hashCode()); + return result; + } + + public String toString() + { + return "Binding{bindingKey="+_bindingKey+", exchange="+_exchange+", queue="+_queue+", id= " + _id + " }"; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerConfigurationStoreCreator.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerConfigurationStoreCreator.java new file mode 100644 index 0000000000..2d42d60039 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerConfigurationStoreCreator.java @@ -0,0 +1,82 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.server.configuration.store.MemoryConfigurationEntryStore; +import org.apache.qpid.server.plugin.ConfigurationStoreFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; + +/** + * A helper class responsible for creation and opening of broker store. + */ +public class BrokerConfigurationStoreCreator +{ + private Map _factories = new HashMap(); + + public BrokerConfigurationStoreCreator() + { + QpidServiceLoader serviceLoader = new QpidServiceLoader(); + Iterable configurationStoreFactories = serviceLoader + .instancesOf(ConfigurationStoreFactory.class); + for (ConfigurationStoreFactory storeFactory : configurationStoreFactories) + { + String type = storeFactory.getType(); + ConfigurationStoreFactory factory = _factories.put(type.toLowerCase(), storeFactory); + if (factory != null) + { + throw new IllegalStateException("ConfigurationStoreFactory with type name '" + type + + "' is already registered using class '" + factory.getClass().getName() + "', can not register class '" + + storeFactory.getClass().getName() + "'"); + } + } + } + + /** + * Create broker configuration store for a given store location, store type, initial json config location + * + * @param storeLocation store location + * @param storeType store type + * @param initialConfigLocation initial store location + * @param overwrite whether to overwrite an existing configuration store with the initial configuration + * @param configProperties a map of configuration properties the store can use to resolve configuration variables + * @throws IllegalConfigurationException if store type is unknown + */ + public ConfigurationEntryStore createStore(String storeLocation, String storeType, String initialConfigLocation, boolean overwrite, Map configProperties) + { + ConfigurationEntryStore initialStore = new MemoryConfigurationEntryStore(initialConfigLocation, null, configProperties); + ConfigurationStoreFactory factory = _factories.get(storeType.toLowerCase()); + if (factory == null) + { + throw new IllegalConfigurationException("Unknown store type: " + storeType); + } + return factory.createStore(storeLocation, initialStore, overwrite, configProperties); + } + + public Collection getStoreTypes() + { + return Collections.unmodifiableCollection(_factories.keySet()); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java new file mode 100644 index 0000000000..fb382a8ca9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/BrokerProperties.java @@ -0,0 +1,81 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration; + +import java.util.Locale; + +/** + * Declares broker system property names + */ +public class BrokerProperties +{ + public static final int DEFAULT_HEARTBEAT_TIMEOUT_FACTOR = 2; + public static final String PROPERTY_HEARTBEAT_TIMEOUT_FACTOR = "qpid.broker_heartbeat_timeout_factor"; + public static final int HEARTBEAT_TIMEOUT_FACTOR = Integer.getInteger(PROPERTY_HEARTBEAT_TIMEOUT_FACTOR, DEFAULT_HEARTBEAT_TIMEOUT_FACTOR); + + public static final String PROPERTY_DEAD_LETTER_EXCHANGE_SUFFIX = "qpid.broker_dead_letter_exchange_suffix"; + public static final String PROPERTY_DEAD_LETTER_QUEUE_SUFFIX = "qpid.broker_dead_letter_queue_suffix"; + + public static final String PROPERTY_MSG_AUTH = "qpid.broker_msg_auth"; + public static final String PROPERTY_STATUS_UPDATES = "qpid.broker_status_updates"; + public static final String PROPERTY_LOCALE = "qpid.broker_locale"; + public static final String PROPERTY_DEFAULT_SUPPORTED_PROTOCOL_REPLY = "qpid.broker_default_supported_protocol_version_reply"; + public static final String PROPERTY_DISABLED_FEATURES = "qpid.broker_disabled_features"; + + private static final int DEFAULT_FRAME_SIZE = 65535; + public static final String PROPERTY_FRAME_SIZE = "qpid.broker_frame_size"; + public static final int FRAME_SIZE = Integer.getInteger(PROPERTY_FRAME_SIZE, DEFAULT_FRAME_SIZE); + + public static final String PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_EXCLUDES = "qpid.broker_default_amqp_protocol_excludes"; + public static final String PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_INCLUDES = "qpid.broker_default_amqp_protocol_includes"; + + public static final String PROPERTY_MANAGEMENT_RIGHTS_INFER_ALL_ACCESS = "qpid.broker_jmx_method_rights_infer_all_access"; + public static final String PROPERTY_USE_CUSTOM_RMI_SOCKET_FACTORY = "qpid.broker_jmx_use_custom_rmi_socket_factory"; + + public static final String PROPERTY_DEFAULT_SHARED_MESSAGE_GROUP = "qpid.broker_default-shared-message-group"; + + public static final String PROPERTY_QPID_HOME = "QPID_HOME"; + public static final String PROPERTY_QPID_WORK = "QPID_WORK"; + public static final String PROPERTY_LOG_RECORDS_BUFFER_SIZE = "qpid.broker_log_records_buffer_size"; + + private BrokerProperties() + { + } + + public static Locale getLocale() + { + Locale locale = Locale.US; + String localeSetting = System.getProperty(BrokerProperties.PROPERTY_LOCALE); + if (localeSetting != null) + { + String[] localeParts = localeSetting.split("_"); + String language = (localeParts.length > 0 ? localeParts[0] : ""); + String country = (localeParts.length > 1 ? localeParts[1] : ""); + String variant = ""; + if (localeParts.length > 2) + { + variant = localeSetting.substring(language.length() + 1 + country.length() + 1); + } + locale = new Locale(language, country, variant); + } + return locale; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntry.java new file mode 100644 index 0000000000..8afb1af24d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntry.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.configuration; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public class ConfigurationEntry +{ + public static final String ATTRIBUTE_NAME = "name"; + + private final UUID _id; + private final String _type; + private final Map _attributes; + private final Set _childrenIds; + private final ConfigurationEntryStore _store; + + public ConfigurationEntry(UUID id, String type, Map attributes, Set childrenIds, + ConfigurationEntryStore store) + { + super(); + _id = id; + _type = type; + _attributes = attributes; + _childrenIds = childrenIds; + _store = store; + } + + public UUID getId() + { + return _id; + } + + public String getType() + { + return _type; + } + + public Map getAttributes() + { + return _attributes; + } + + public Set getChildrenIds() + { + return _childrenIds; + } + + public ConfigurationEntryStore getStore() + { + return _store; + } + + /** + * Returns this entry's children. The collection should not be modified. + */ + public Map> getChildren() + { + Map> children = null; + if (_childrenIds == null) + { + children = Collections.emptyMap(); + } + else + { + children = new HashMap>(); + for (UUID childId : _childrenIds) + { + ConfigurationEntry entry = _store.getEntry(childId); + String type = entry.getType(); + Collection childrenOfType = children.get(type); + if (childrenOfType == null) + { + childrenOfType = new ArrayList(); + children.put(type, childrenOfType); + } + childrenOfType.add(entry); + } + } + return Collections.unmodifiableMap(children); + } + + public boolean hasChild(UUID id) + { + return _childrenIds.contains(id); + } + + @Override + public int hashCode() + { + return _id.hashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + + ConfigurationEntry other = (ConfigurationEntry) obj; + if (_id == null) + { + if (other._id != null) + { + return false; + } + } + else if (!_id.equals(other._id)) + { + return false; + } + + if (_type == null) + { + if (other._type != null) + { + return false; + } + } + else if (!_type.equals(other._type)) + { + return false; + } + + if (_store == null) + { + if (other._store != null) + { + return false; + } + } + else if (!_store.equals(other._store)) + { + return false; + } + + if (_childrenIds == null) + { + if (other._childrenIds != null) + { + return false; + } + } + else if (!_childrenIds.equals(other._childrenIds)) + { + return false; + } + + if (_attributes == null) + { + if (other._attributes != null) + { + return false; + } + } + else if (!_attributes.equals(other._attributes)) + { + return false; + } + return true; + } + + @Override + public String toString() + { + return "ConfigurationEntry [id=" + _id + ", type=" + _type + ", attributes=" + _attributes + ", childrenIds=" + + _childrenIds + "]"; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntryStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntryStore.java new file mode 100644 index 0000000000..5f3589c7ef --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfigurationEntryStore.java @@ -0,0 +1,87 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration; + +import java.util.UUID; + +public interface ConfigurationEntryStore +{ + + /** + * Returns stored root configuration entry + * + * @return root entry + */ + ConfigurationEntry getRootEntry(); + + /** + * Returns the configuration entry with a given id. + * + * @return entry with a given id or null if entry does not exists + */ + ConfigurationEntry getEntry(UUID id); + + /** + * Saves given entries in the store. + * + * @param entries entries to store + * @throws IllegalConfigurationException if save operation fails + */ + void save(ConfigurationEntry... entries); + + /** + * Removes the entries with given IDs and all their children + * + * @param entryIds IDs of entries to remove + * @return IDs of removed entries + * @throws IllegalConfigurationException if remove operation fails + */ + UUID[] remove(UUID... entryIds); + + /** + * Copies the store into the given location + * + * @param target location to copy store into + * @throws IllegalConfigurationException if store cannot be copied into given location + */ + void copyTo(String copyLocation); + + /** + * Return the store location for the opened store or null if store has not been opened. + * + * @return store location for the opened store or null if store has not been opened + */ + String getStoreLocation(); + + /** + * Returns the version of the store + * + * @return store version + */ + int getVersion(); + + /** + * Returns the type of the store + * + * @return store type + */ + String getType(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfiguredObjectRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfiguredObjectRecoverer.java new file mode 100644 index 0000000000..65d97e6db1 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ConfiguredObjectRecoverer.java @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration; + +import org.apache.qpid.server.model.ConfiguredObject; + +public interface ConfiguredObjectRecoverer +{ + T create(RecovererProvider recovererProvider, ConfigurationEntry entry, ConfiguredObject... parents); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java new file mode 100644 index 0000000000..c7cf0c0892 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java @@ -0,0 +1,58 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration; + +import org.apache.commons.configuration.Configuration; + + +public class ExchangeConfiguration +{ + + private Configuration _config; + private String _name; + + public ExchangeConfiguration(String exchName, Configuration subset) + { + _name = exchName; + _config = subset; + } + + public String getName() + { + return _name; + } + + public String getType() + { + return _config.getString("type","direct"); + } + + public boolean getDurable() + { + return _config.getBoolean("durable", false); + } + + public boolean getAutoDelete() + { + return _config.getBoolean("autodelete",false); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/IllegalConfigurationException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/IllegalConfigurationException.java new file mode 100644 index 0000000000..bedd470ddf --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/IllegalConfigurationException.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.configuration; + +public class IllegalConfigurationException extends RuntimeException +{ + private static final long serialVersionUID = 1130064756291179812L; + + public IllegalConfigurationException(String message) + { + super(message); + } + + public IllegalConfigurationException(String message, Throwable cause) + { + super(message, cause); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java new file mode 100644 index 0000000000..25466d9c55 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java @@ -0,0 +1,211 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration; + +import java.util.Collections; +import java.util.Map; +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; + +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.server.configuration.plugins.AbstractConfiguration; + +import java.util.List; + +public class QueueConfiguration extends AbstractConfiguration +{ + private String _name; + private VirtualHostConfiguration _vHostConfig; + + public QueueConfiguration(String name, VirtualHostConfiguration virtualHostConfiguration) throws ConfigurationException + { + _vHostConfig = virtualHostConfiguration; + _name = name; + + CompositeConfiguration mungedConf = new CompositeConfiguration(); + mungedConf.addConfiguration(_vHostConfig.getConfig().subset("queues.queue." + escapeTagName(name))); + mungedConf.addConfiguration(_vHostConfig.getConfig().subset("queues")); + + setConfiguration("virtualhosts.virtualhost.queues.queue", mungedConf); + } + + public String[] getElementsProcessed() + { + return new String[]{"maximumMessageSize", + "maximumQueueDepth", + "maximumMessageCount", + "maximumMessageAge", + "minimumAlertRepeatGap", + "durable", + "exchange", + "exclusive", + "queue", + "autodelete", + "priority", + "priorities", + "routingKey", + "capacity", + "flowResumeCapacity", + "lvq", + "lvqKey", + "sortKey", + "maximumDeliveryCount", + "deadLetterQueues", + "argument" + }; + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + //Currently doesn't do validation + } + + public VirtualHostConfiguration getVirtualHostConfiguration() + { + return _vHostConfig; + } + + public boolean getDurable() + { + return getBooleanValue("durable"); + } + + public boolean getExclusive() + { + return getBooleanValue("exclusive"); + } + + public boolean getAutoDelete() + { + return getBooleanValue("autodelete"); + } + + public String getOwner() + { + return getStringValue("owner", null); + } + + public boolean getPriority() + { + return getBooleanValue("priority"); + } + + public int getPriorities() + { + return getIntValue("priorities", -1); + } + + public String getExchange() + { + return getStringValue("exchange", ExchangeDefaults.DEFAULT_EXCHANGE_NAME); + } + + public List getRoutingKeys() + { + return getListValue("routingKey"); + } + + public String getName() + { + return _name; + } + + public String getDescription() + { + return getStringValue("description"); + } + + public int getMaximumMessageAge() + { + return getIntValue("maximumMessageAge", _vHostConfig.getMaximumMessageAge()); + } + + public long getMaximumQueueDepth() + { + return getLongValue("maximumQueueDepth", _vHostConfig.getMaximumQueueDepth()); + } + + public long getMaximumMessageSize() + { + return getLongValue("maximumMessageSize", _vHostConfig.getMaximumMessageSize()); + } + + public long getMaximumMessageCount() + { + return getLongValue("maximumMessageCount", _vHostConfig.getMaximumMessageCount()); + } + + public long getMinimumAlertRepeatGap() + { + return getLongValue("minimumAlertRepeatGap", _vHostConfig.getMinimumAlertRepeatGap()); + } + + public long getCapacity() + { + return getLongValue("capacity", _vHostConfig.getCapacity()); + } + + public long getFlowResumeCapacity() + { + return getLongValue("flowResumeCapacity", _vHostConfig.getFlowResumeCapacity()); + } + + public boolean isLVQ() + { + return getBooleanValue("lvq"); + } + + public String getLVQKey() + { + return getStringValue("lvqKey", null); + } + + public String getQueueSortKey() + { + return getStringValue("sortKey", null); + } + + public int getMaxDeliveryCount() + { + return getIntValue("maximumDeliveryCount", _vHostConfig.getMaxDeliveryCount()); + } + + /** + * Check if dead letter queue delivery is enabled, deferring to the virtualhost configuration if not set. + */ + public boolean isDeadLetterQueueEnabled() + { + return getBooleanValue("deadLetterQueues", _vHostConfig.isDeadLetterQueueEnabled()); + } + + public Map getArguments() + { + return getMap("argument"); + } + + public Map getBindingArguments(String routingKey) + { + + return getConfig().containsKey(routingKey+".bindingArgument") ? getMap(routingKey+".bindingArgument") : null; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/RecovererProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/RecovererProvider.java new file mode 100644 index 0000000000..963d019ec3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/RecovererProvider.java @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration; + +import org.apache.qpid.server.model.ConfiguredObject; + +public interface RecovererProvider +{ + ConfiguredObjectRecoverer getRecoverer(String type); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java new file mode 100644 index 0000000000..189f5916e0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.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.server.configuration; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; + +import org.apache.qpid.server.configuration.plugins.AbstractConfiguration; +import org.apache.qpid.server.model.Broker; + +import java.io.File; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class VirtualHostConfiguration extends AbstractConfiguration +{ + private final String _name; + private final Map _queues = new HashMap(); + private final Map _exchanges = new HashMap(); + private final Broker _broker; + private final long _defaultHouseKeepingCheckPeriod; + + public VirtualHostConfiguration(String name, Configuration config, Broker broker) throws ConfigurationException + { + _name = name; + _broker = broker; + + // store value of this attribute for running life of virtual host since updating of this value has no run-time effect + _defaultHouseKeepingCheckPeriod = ((Number)_broker.getAttribute(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD)).longValue(); + setConfiguration(config); + } + + public VirtualHostConfiguration(String name, File configurationFile, Broker broker) throws ConfigurationException + { + this(name, loadConfiguration(name, configurationFile), broker); + } + + private static Configuration loadConfiguration(String name, File configurationFile) throws ConfigurationException + { + Configuration configuration = null; + if (configurationFile == null) + { + throw new IllegalConfigurationException("Virtualhost configuration file must be supplied!"); + } + else + { + Configuration virtualHostConfig = XmlConfigurationUtilities.parseConfig(configurationFile); + + // check for the element with the same name as virtual host + Configuration config = virtualHostConfig.subset("virtualhost." + XmlConfigurationUtilities.escapeTagName(name)); + if (config.isEmpty()) + { + throw new IllegalConfigurationException("No configuration found for virtual host '" + name + "' in " + configurationFile.getAbsolutePath()); + } + else + { + configuration = config; + } + } + return configuration; + } + + /** + * Apply the given configuration to this VirtualHostConfiguration + * + * @param config the config to apply + * @throws ConfigurationException if a problem occurs with configuration + */ + public void setConfiguration(Configuration config) throws ConfigurationException + { + setConfiguration("virtualhosts.virtualhost", config); + + Iterator i = getListValue("queues.queue.name").iterator(); + + while (i.hasNext()) + { + String queueName = (String) i.next(); + _queues.put(queueName, new QueueConfiguration(queueName, this)); + } + + i = getListValue("exchanges.exchange.name").iterator(); + int count = 0; + while (i.hasNext()) + { + CompositeConfiguration mungedConf = new CompositeConfiguration(); + mungedConf.addConfiguration(config.subset("exchanges.exchange(" + count++ + ")")); + mungedConf.addConfiguration(getConfig().subset("exchanges")); + String exchName = (String) i.next(); + _exchanges.put(exchName, new ExchangeConfiguration(exchName, mungedConf)); + } + } + + public String getName() + { + return _name; + } + + public long getHousekeepingCheckPeriod() + { + return getLongValue("housekeeping.checkPeriod", _defaultHouseKeepingCheckPeriod); + } + + public Configuration getStoreConfiguration() + { + return getConfig().subset("store"); + } + + public String getMessageStoreClass() + { + return getStringValue("store.class", null); + } + + public void setMessageStoreClass(String storeFactoryClass) + { + getConfig().setProperty("store.class", storeFactoryClass); + } + + public List getExchanges() + { + return getListValue("exchanges.exchange.name"); + } + + public String[] getQueueNames() + { + return _queues.keySet().toArray(new String[_queues.size()]); + } + + public ExchangeConfiguration getExchangeConfiguration(String exchangeName) + { + return _exchanges.get(exchangeName); + } + + public QueueConfiguration getQueueConfiguration(String queueName) + { + // We might be asked for the config for a queue we don't know about, + // such as one that's been dynamically created. Those get the defaults by default. + if (_queues.containsKey(queueName)) + { + return _queues.get(queueName); + } + else + { + try + { + return new QueueConfiguration(queueName, this); + } + catch (ConfigurationException e) + { + // The configuration is empty so there can't be an error. + return null; + } + } + } + + public int getMaximumMessageAge() + { + return getIntValue("queues.maximumMessageAge", getBrokerAttributeAsInt(Broker.QUEUE_ALERT_THRESHOLD_MESSAGE_AGE)); + } + + public Long getMaximumQueueDepth() + { + return getLongValue("queues.maximumQueueDepth", getBrokerAttributeAsLong(Broker.QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES)); + } + + public Long getMaximumMessageSize() + { + return getLongValue("queues.maximumMessageSize", getBrokerAttributeAsLong(Broker.QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE)); + } + + public Long getMaximumMessageCount() + { + return getLongValue("queues.maximumMessageCount", getBrokerAttributeAsLong(Broker.QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES)); + } + + public Long getMinimumAlertRepeatGap() + { + return getLongValue("queues.minimumAlertRepeatGap", getBrokerAttributeAsLong(Broker.QUEUE_ALERT_REPEAT_GAP)); + } + + public long getCapacity() + { + return getLongValue("queues.capacity", getBrokerAttributeAsLong(Broker.QUEUE_FLOW_CONTROL_SIZE_BYTES)); + } + + public long getFlowResumeCapacity() + { + return getLongValue("queues.flowResumeCapacity", getBrokerAttributeAsLong(Broker.QUEUE_FLOW_CONTROL_RESUME_SIZE_BYTES)); + } + + public String[] getElementsProcessed() + { + return new String[]{"queues", "exchanges", "custom-exchanges", "store", "housekeeping"}; + + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + // QPID-3249. Support for specifying authentication name at vhost level is no longer supported. + if (getListValue("security.authentication.name").size() > 0) + { + String message = "Validation error : security/authentication/name is no longer a supported element within the configuration xml." + + " It appears in virtual host definition : " + _name; + throw new ConfigurationException(message); + } + + // QPID-3266. Tidy up housekeeping configuration option for scheduling frequency + if (contains("housekeeping.expiredMessageCheckPeriod")) + { + String message = "Validation error : housekeeping/expiredMessageCheckPeriod must be replaced by housekeeping/checkPeriod." + + " It appears in virtual host definition : " + _name; + throw new ConfigurationException(message); + } + } + + public int getHouseKeepingThreadCount() + { + return getIntValue("housekeeping.poolSize", Runtime.getRuntime().availableProcessors()); + } + + public long getTransactionTimeoutOpenWarn() + { + return getLongValue("transactionTimeout.openWarn", + getBrokerAttributeAsLong(Broker.VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_WARN)); + } + + public long getTransactionTimeoutOpenClose() + { + return getLongValue("transactionTimeout.openClose", + getBrokerAttributeAsLong(Broker.VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE)); + } + + public long getTransactionTimeoutIdleWarn() + { + return getLongValue("transactionTimeout.idleWarn", + getBrokerAttributeAsLong(Broker.VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_WARN)); + } + + public long getTransactionTimeoutIdleClose() + { + return getLongValue("transactionTimeout.idleClose", + getBrokerAttributeAsLong(Broker.VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE)); + } + + public int getMaxDeliveryCount() + { + return getIntValue("queues.maximumDeliveryCount", getBrokerAttributeAsInt(Broker.QUEUE_MAXIMUM_DELIVERY_ATTEMPTS)); + } + + /** + * Check if dead letter queue delivery is enabled, deferring to the broker configuration if not set. + */ + public boolean isDeadLetterQueueEnabled() + { + return getBooleanValue("queues.deadLetterQueues", getBrokerAttributeAsBoolean(Broker.QUEUE_DEAD_LETTER_QUEUE_ENABLED)); + } + + private long getBrokerAttributeAsLong(String name) + { + Number brokerValue = (Number)_broker.getAttribute(name); + return brokerValue == null? 0 : brokerValue.longValue(); + } + + private int getBrokerAttributeAsInt(String name) + { + Number brokerValue = (Number)_broker.getAttribute(name); + return brokerValue == null? 0 : brokerValue.intValue(); + } + + private boolean getBrokerAttributeAsBoolean(String name) + { + Boolean brokerValue = (Boolean)_broker.getAttribute(name); + return brokerValue == null? false : brokerValue.booleanValue(); + } + + public String getType() + { + return getStringValue("type", "org.apache.qpid.server.store.berkeleydb.BDBHAMessageStore".equals(getMessageStoreClass()) ? "BDB_HA": "STANDARD"); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/XmlConfigurationUtilities.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/XmlConfigurationUtilities.java new file mode 100644 index 0000000000..84972c1e0a --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/XmlConfigurationUtilities.java @@ -0,0 +1,93 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration; + +import java.io.File; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.ConfigurationFactory; +import org.apache.commons.configuration.SystemConfiguration; +import org.apache.commons.configuration.XMLConfiguration; + +public class XmlConfigurationUtilities +{ + + // Our configuration class needs to make the interpolate method + // public so it can be called below from the config method. + public static class MyConfiguration extends CompositeConfiguration + { + public String interpolate(String obj) + { + return super.interpolate(obj); + } + } + + public static Configuration parseConfig(File file) throws ConfigurationException + { + ConfigurationFactory factory = new ConfigurationFactory(); + factory.setConfigurationFileName(file.getAbsolutePath()); + Configuration conf = factory.getConfiguration(); + + Iterator keys = conf.getKeys(); + if (!keys.hasNext()) + { + keys = null; + conf = flatConfig(file); + } + + return conf; + } + + public final static Configuration flatConfig(File file) throws ConfigurationException + { + // We have to override the interpolate methods so that + // interpolation takes place across the entirety of the + // composite configuration. Without doing this each + // configuration object only interpolates variables defined + // inside itself. + final MyConfiguration conf = new MyConfiguration(); + conf.addConfiguration(new SystemConfiguration() + { + protected String interpolate(String o) + { + return conf.interpolate(o); + } + }); + conf.addConfiguration(new XMLConfiguration(file) + { + protected String interpolate(String o) + { + return conf.interpolate(o); + } + }); + return conf; + } + + public static String escapeTagName(String name) + { + return name.replaceAll("\\.", "\\.\\."); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/plugins/AbstractConfiguration.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/plugins/AbstractConfiguration.java new file mode 100644 index 0000000000..b87022868e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/plugins/AbstractConfiguration.java @@ -0,0 +1,344 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.plugins; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.ConversionException; +import org.apache.log4j.Logger; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +public abstract class AbstractConfiguration +{ + protected static final Logger _logger = Logger.getLogger(AbstractConfiguration.class); + + private Configuration _config; + + /** + * The Elements that this Plugin can process. + * + * For a Queues plugin that would be a list containing: + *

    + *
  • queue - the queue entries + *
  • the alerting values for defaults + *
  • exchange - the default exchange + *
  • durable - set the default durablity + *
+ */ + abstract public String[] getElementsProcessed(); + + /** Performs configuration validation. */ + public void validateConfiguration() throws ConfigurationException + { + // Override in sub-classes + } + + public Configuration getConfig() + { + return _config; + } + + /** + * Sets the configuration for this plugin + * + * @param path + * @param configuration the configuration for this plugin. + */ + public void setConfiguration(String path, Configuration configuration) throws ConfigurationException + { + _config = configuration; + + // Extract a list of elements for processing + Iterator keys = configuration.getKeys(); + + Set elements = new HashSet(); + while (keys.hasNext()) + { + String key = (String) keys.next(); + + int elementNameIndex = key.indexOf("."); + + String element = key.trim(); + if (elementNameIndex != -1) + { + element = key.substring(0, elementNameIndex).trim(); + } + + // Trim any element properties + elementNameIndex = element.indexOf("["); + if (elementNameIndex > 0) + { + element = element.substring(0, elementNameIndex).trim(); + } + + elements.add(element); + } + + //Remove the items we already expect in the configuration + for (String tag : getElementsProcessed()) + { + + // Work round the issue with Commons configuration. + // With an XMLConfiguration the key will be [@property] + // but with a CompositeConfiguration it will be @property]. + // Hide this issue from our users so when/if we change the + // configuration they don't have to. + int bracketIndex = tag.indexOf("["); + if (bracketIndex != -1) + { + tag = tag.substring(bracketIndex + 1, tag.length()); + } + + elements.remove(tag); + } + + if (_logger.isInfoEnabled()) + { + if (!elements.isEmpty()) + { + _logger.info("Elements to lookup:" + path); + for (String tag : elements) + { + _logger.info("Tag:'" + tag + "'"); + } + } + } + + validateConfiguration(); + } + + /** Helper method to print out list of keys in a {@link Configuration}. */ + public static final void showKeys(Configuration config) + { + if (config.isEmpty()) + { + _logger.info("Configuration is empty"); + } + else + { + Iterator keys = config.getKeys(); + while (keys.hasNext()) + { + String key = (String) keys.next(); + _logger.info("Configuration key: " + key); + } + } + } + + protected boolean hasConfiguration() + { + return _config != null; + } + + /// Getters + + protected double getDoubleValue(String property) + { + return getDoubleValue(property, 0.0); + } + + protected double getDoubleValue(String property, double defaultValue) + { + return _config.getDouble(property, defaultValue); + } + + protected long getLongValue(String property) + { + return getLongValue(property, 0); + } + + protected long getLongValue(String property, long defaultValue) + { + return _config.getLong(property, defaultValue); + } + + protected int getIntValue(String property) + { + return getIntValue(property, 0); + } + + protected int getIntValue(String property, int defaultValue) + { + return _config.getInt(property, defaultValue); + } + + protected String getStringValue(String property) + { + return getStringValue(property, null); + } + + protected String getStringValue(String property, String defaultValue) + { + return _config.getString(property, defaultValue); + } + + protected boolean getBooleanValue(String property) + { + return getBooleanValue(property, false); + } + + protected boolean getBooleanValue(String property, boolean defaultValue) + { + return _config.getBoolean(property, defaultValue); + } + + protected List getListValue(String property) + { + return getListValue(property, Collections.EMPTY_LIST); + } + + protected List getListValue(String property, List defaultValue) + { + return _config.getList(property, defaultValue); + } + + /// Validation Helpers + + protected boolean contains(String property) + { + return _config.getProperty(property) != null; + } + + /** + * Provide mechanism to validate Configuration contains a Postiive Long Value + * + * @param property + * + * @throws ConfigurationException + */ + protected void validatePositiveLong(String property) throws ConfigurationException + { + try + { + if (!containsPositiveLong(property)) + { + throw new ConfigurationException(this.getClass().getSimpleName() + + ": '" + property + + "' must be a Positive Long value."); + } + } + catch (Exception e) + { + Throwable last = e; + + // Find the first cause + if (e instanceof ConversionException) + { + Throwable t = e.getCause(); + while (t != null) + { + last = t; + t = last.getCause(); + } + } + + throw new ConfigurationException(this.getClass().getSimpleName() + + ": unable to configure invalid " + + property + ":" + + _config.getString(property), + last); + } + } + + protected boolean containsLong(String property) + { + try + { + _config.getLong(property); + return true; + } + catch (NoSuchElementException e) + { + return false; + } + } + + protected boolean containsPositiveLong(String property) + { + try + { + long value = _config.getLong(property); + return value > 0; + } + catch (NoSuchElementException e) + { + return false; + } + + } + + protected boolean containsInt(String property) + { + try + { + _config.getInt(property); + return true; + } + catch (NoSuchElementException e) + { + return false; + } + } + + protected boolean containsBoolean(String property) + { + try + { + _config.getBoolean(property); + return true; + } + catch (NoSuchElementException e) + { + return false; + } + } + + public static String escapeTagName(String name) + { + return name.replaceAll("\\.", "\\.\\."); + } + + protected void setConfig(Configuration config) + { + _config = config; + } + + protected Map getMap(String name) + { + List elements = getListValue(name,Collections.emptyList()); + + Map map = new LinkedHashMap(); + for(Object item : elements) + { + String[] keyValue = String.valueOf(item).split("=",2); + map.put(keyValue[0].trim(), keyValue.length > 1 ? keyValue[1].trim() : null); + } + return map; + } +} + + diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/AccessControlProviderRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/AccessControlProviderRecoverer.java new file mode 100644 index 0000000000..df80b9fe5f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/AccessControlProviderRecoverer.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.configuration.startup; + +import java.util.Map; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.model.AccessControlProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.adapter.AccessControlProviderFactory; + +public class AccessControlProviderRecoverer implements ConfiguredObjectRecoverer +{ + private final AccessControlProviderFactory _accessControlProviderFactory; + + public AccessControlProviderRecoverer(AccessControlProviderFactory authenticationProviderFactory) + { + _accessControlProviderFactory = authenticationProviderFactory; + } + + @Override + public AccessControlProvider create(RecovererProvider recovererProvider, ConfigurationEntry configurationEntry, ConfiguredObject... parents) + { + Broker broker = RecovererHelper.verifyOnlyBrokerIsParent(parents); + Map attributes = configurationEntry.getAttributes(); + AccessControlProvider authenticationProvider = _accessControlProviderFactory.recover( + configurationEntry.getId(), + broker, + attributes); + + return authenticationProvider; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecoverer.java new file mode 100644 index 0000000000..8eec88d556 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecoverer.java @@ -0,0 +1,95 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration.startup; + +import java.util.Collection; +import java.util.Map; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.configuration.store.StoreConfigurationChangeListener; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.model.adapter.AuthenticationProviderFactory; + +public class AuthenticationProviderRecoverer implements ConfiguredObjectRecoverer +{ + private final AuthenticationProviderFactory _authenticationProviderFactory; + private final StoreConfigurationChangeListener _storeChangeListener; + + public AuthenticationProviderRecoverer(AuthenticationProviderFactory authenticationProviderFactory, StoreConfigurationChangeListener storeChangeListener) + { + _authenticationProviderFactory = authenticationProviderFactory; + _storeChangeListener = storeChangeListener; + } + + @Override + public AuthenticationProvider create(RecovererProvider recovererProvider, ConfigurationEntry configurationEntry, ConfiguredObject... parents) + { + Broker broker = RecovererHelper.verifyOnlyBrokerIsParent(parents); + Map attributes = configurationEntry.getAttributes(); + AuthenticationProvider authenticationProvider = _authenticationProviderFactory.recover(configurationEntry.getId(), attributes, broker); + + Map> childEntries = configurationEntry.getChildren(); + + for (String type : childEntries.keySet()) + { + recoverType(recovererProvider, _storeChangeListener, authenticationProvider, childEntries, type); + } + + return authenticationProvider; + } + + private void recoverType(RecovererProvider recovererProvider, + StoreConfigurationChangeListener storeChangeListener, + AuthenticationProvider authenticationProvider, + Map> childEntries, + String type) + { + ConfiguredObjectRecoverer recoverer = recovererProvider.getRecoverer(type); + if (recoverer == null) + { + throw new IllegalConfigurationException("Cannot recover entry for the type '" + type + "' from broker"); + } + Collection entries = childEntries.get(type); + for (ConfigurationEntry childEntry : entries) + { + ConfiguredObject object = recoverer.create(recovererProvider, childEntry, authenticationProvider); + if (object == null) + { + throw new IllegalConfigurationException("Cannot create configured object for the entry " + childEntry); + } + if (object instanceof PreferencesProvider) + { + authenticationProvider.setPreferencesProvider((PreferencesProvider)object); + } + else + { + throw new IllegalConfigurationException("Cannot associate " + object + " with authentication provider " + authenticationProvider); + } + object.addChangeListener(storeChangeListener); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java new file mode 100644 index 0000000000..4923565e26 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/BrokerRecoverer.java @@ -0,0 +1,207 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration.startup; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.configuration.store.StoreConfigurationChangeListener; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.logging.LogRecorder; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Model; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.model.adapter.AccessControlProviderFactory; +import org.apache.qpid.server.model.adapter.AuthenticationProviderFactory; +import org.apache.qpid.server.model.adapter.BrokerAdapter; +import org.apache.qpid.server.model.adapter.GroupProviderFactory; +import org.apache.qpid.server.model.adapter.PortFactory; +import org.apache.qpid.server.model.adapter.PreferencesProviderCreator; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.util.MapValueConverter; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public class BrokerRecoverer implements ConfiguredObjectRecoverer +{ + private static final Pattern MODEL_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+$"); + + private final StatisticsGatherer _statisticsGatherer; + private final VirtualHostRegistry _virtualHostRegistry; + private final LogRecorder _logRecorder; + private final RootMessageLogger _rootMessageLogger; + private final AuthenticationProviderFactory _authenticationProviderFactory; + private final AccessControlProviderFactory _accessControlProviderFactory; + private final PortFactory _portFactory; + private final TaskExecutor _taskExecutor; + private final BrokerOptions _brokerOptions; + private final GroupProviderFactory _groupProviderFactory; + private final StoreConfigurationChangeListener _storeChangeListener; + private final PreferencesProviderCreator _preferencesProviderCreator; + + public BrokerRecoverer(AuthenticationProviderFactory authenticationProviderFactory, GroupProviderFactory groupProviderFactory, + AccessControlProviderFactory accessControlProviderFactory, PortFactory portFactory, PreferencesProviderCreator preferencesProviderFactory, StatisticsGatherer statisticsGatherer, + VirtualHostRegistry virtualHostRegistry, LogRecorder logRecorder, RootMessageLogger rootMessageLogger, TaskExecutor taskExecutor, + BrokerOptions brokerOptions, StoreConfigurationChangeListener storeChangeListener) + { + _groupProviderFactory = groupProviderFactory; + _portFactory = portFactory; + _authenticationProviderFactory = authenticationProviderFactory; + _accessControlProviderFactory = accessControlProviderFactory; + _statisticsGatherer = statisticsGatherer; + _virtualHostRegistry = virtualHostRegistry; + _logRecorder = logRecorder; + _rootMessageLogger = rootMessageLogger; + _taskExecutor = taskExecutor; + _brokerOptions = brokerOptions; + _storeChangeListener = storeChangeListener; + _preferencesProviderCreator = preferencesProviderFactory; + } + + @Override + public Broker create(RecovererProvider recovererProvider, ConfigurationEntry entry, ConfiguredObject... parents) + { + //Map attributes = entry.getAttributes(); + Map attributesCopy = validateAttributes(entry); + + attributesCopy.put(Broker.MODEL_VERSION, Model.MODEL_VERSION); + + BrokerAdapter broker = new BrokerAdapter(entry.getId(), attributesCopy, _statisticsGatherer, _virtualHostRegistry, + _logRecorder, _rootMessageLogger, _authenticationProviderFactory,_groupProviderFactory, _accessControlProviderFactory, + _portFactory , _preferencesProviderCreator, _taskExecutor, entry.getStore(), _brokerOptions); + + broker.addChangeListener(_storeChangeListener); + + //Recover the SSL keystores / truststores first, then others that depend on them + Map> childEntries = new HashMap>(entry.getChildren()); + Map> priorityChildEntries = new HashMap>(childEntries); + List types = new ArrayList(childEntries.keySet()); + + for(String type : types) + { + if(KeyStore.class.getSimpleName().equals(type) || TrustStore.class.getSimpleName().equals(type) + || AuthenticationProvider.class.getSimpleName().equals(type)) + { + childEntries.remove(type); + } + else + { + priorityChildEntries.remove(type); + } + } + + for (String type : priorityChildEntries.keySet()) + { + recoverType(recovererProvider, _storeChangeListener, broker, priorityChildEntries, type); + } + for (String type : childEntries.keySet()) + { + recoverType(recovererProvider, _storeChangeListener, broker, childEntries, type); + } + + return broker; + } + + private Map validateAttributes(ConfigurationEntry entry) + { + Map attributes = entry.getAttributes(); + + String modelVersion = null; + if (attributes.containsKey(Broker.MODEL_VERSION)) + { + modelVersion = MapValueConverter.getStringAttribute(Broker.MODEL_VERSION, attributes, null); + } + + if (modelVersion == null) + { + throw new IllegalConfigurationException("Broker " + Broker.MODEL_VERSION + " must be specified"); + } + + if (!MODEL_VERSION_PATTERN.matcher(modelVersion).matches()) + { + throw new IllegalConfigurationException("Broker " + Broker.MODEL_VERSION + " is specified in incorrect format: " + + modelVersion); + } + + int versionSeparatorPosition = modelVersion.indexOf("."); + String majorVersionPart = modelVersion.substring(0, versionSeparatorPosition); + int majorModelVersion = Integer.parseInt(majorVersionPart); + int minorModelVersion = Integer.parseInt(modelVersion.substring(versionSeparatorPosition + 1)); + + if (majorModelVersion != Model.MODEL_MAJOR_VERSION || minorModelVersion > Model.MODEL_MINOR_VERSION) + { + throw new IllegalConfigurationException("The model version '" + modelVersion + + "' in configuration is incompatible with the broker model version '" + Model.MODEL_VERSION + "'"); + } + + if(!Model.MODEL_VERSION.equals(modelVersion)) + { + String oldVersion; + do + { + oldVersion = modelVersion; + StoreUpgrader.upgrade(entry.getStore()); + entry = entry.getStore().getRootEntry(); + attributes = entry.getAttributes(); + modelVersion = MapValueConverter.getStringAttribute(Broker.MODEL_VERSION, attributes, null); + } + while(!(modelVersion.equals(oldVersion) || modelVersion.equals(Model.MODEL_VERSION))); + } + + return new HashMap(attributes); + } + + private void recoverType(RecovererProvider recovererProvider, + StoreConfigurationChangeListener storeChangeListener, + BrokerAdapter broker, + Map> childEntries, + String type) + { + ConfiguredObjectRecoverer recoverer = recovererProvider.getRecoverer(type); + if (recoverer == null) + { + throw new IllegalConfigurationException("Cannot recover entry for the type '" + type + "' from broker"); + } + Collection entries = childEntries.get(type); + for (ConfigurationEntry childEntry : entries) + { + ConfiguredObject object = recoverer.create(recovererProvider, childEntry, broker); + if (object == null) + { + throw new IllegalConfigurationException("Cannot create configured object for the entry " + childEntry); + } + broker.recoverChild(object); + object.addChangeListener(storeChangeListener); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProvider.java new file mode 100644 index 0000000000..67268c9854 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProvider.java @@ -0,0 +1,136 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration.startup; + +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.logging.LogRecorder; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.model.AccessControlProvider; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Plugin; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.adapter.AccessControlProviderFactory; +import org.apache.qpid.server.model.adapter.AuthenticationProviderFactory; +import org.apache.qpid.server.model.adapter.GroupProviderFactory; +import org.apache.qpid.server.model.adapter.PortFactory; +import org.apache.qpid.server.model.adapter.PreferencesProviderCreator; +import org.apache.qpid.server.configuration.store.StoreConfigurationChangeListener; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.plugin.AccessControlFactory; +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.plugin.GroupManagerFactory; +import org.apache.qpid.server.plugin.PluginFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public class DefaultRecovererProvider implements RecovererProvider +{ + + private final StatisticsGatherer _brokerStatisticsGatherer; + private final VirtualHostRegistry _virtualHostRegistry; + private final LogRecorder _logRecorder; + private final RootMessageLogger _rootMessageLogger; + private final AuthenticationProviderFactory _authenticationProviderFactory; + private final AccessControlProviderFactory _accessControlProviderFactory; + private final PortFactory _portFactory; + private final GroupProviderFactory _groupProviderFactory; + private final QpidServiceLoader _pluginFactoryServiceLoader; + private final TaskExecutor _taskExecutor; + private final BrokerOptions _brokerOptions; + private final StoreConfigurationChangeListener _storeChangeListener; + private final PreferencesProviderCreator _preferencesProviderCreator; + + public DefaultRecovererProvider(StatisticsGatherer brokerStatisticsGatherer, VirtualHostRegistry virtualHostRegistry, + LogRecorder logRecorder, RootMessageLogger rootMessageLogger, TaskExecutor taskExecutor, BrokerOptions brokerOptions, StoreConfigurationChangeListener storeChangeListener) + { + _preferencesProviderCreator = new PreferencesProviderCreator(); + _authenticationProviderFactory = new AuthenticationProviderFactory(new QpidServiceLoader(), _preferencesProviderCreator); + _accessControlProviderFactory = new AccessControlProviderFactory(new QpidServiceLoader()); + _groupProviderFactory = new GroupProviderFactory(new QpidServiceLoader()); + _portFactory = new PortFactory(); + _brokerStatisticsGatherer = brokerStatisticsGatherer; + _virtualHostRegistry = virtualHostRegistry; + _logRecorder = logRecorder; + _rootMessageLogger = rootMessageLogger; + _pluginFactoryServiceLoader = new QpidServiceLoader(); + _taskExecutor = taskExecutor; + _brokerOptions = brokerOptions; + _storeChangeListener = storeChangeListener; + } + + @Override + public ConfiguredObjectRecoverer getRecoverer(String type) + { + if (Broker.class.getSimpleName().equals(type)) + { + return new BrokerRecoverer(_authenticationProviderFactory, _groupProviderFactory, _accessControlProviderFactory, _portFactory, _preferencesProviderCreator, + _brokerStatisticsGatherer, _virtualHostRegistry, _logRecorder, _rootMessageLogger, _taskExecutor, _brokerOptions, _storeChangeListener); + } + else if(VirtualHost.class.getSimpleName().equals(type)) + { + return new VirtualHostRecoverer(_brokerStatisticsGatherer); + } + else if(AccessControlProvider.class.getSimpleName().equals(type)) + { + return new AccessControlProviderRecoverer(_accessControlProviderFactory); + } + else if(AuthenticationProvider.class.getSimpleName().equals(type)) + { + return new AuthenticationProviderRecoverer(_authenticationProviderFactory, _storeChangeListener); + } + else if(Port.class.getSimpleName().equals(type)) + { + return new PortRecoverer(_portFactory); + } + else if(GroupProvider.class.getSimpleName().equals(type)) + { + return new GroupProviderRecoverer(_groupProviderFactory); + } + else if(KeyStore.class.getSimpleName().equals(type)) + { + return new KeyStoreRecoverer(); + } + else if(TrustStore.class.getSimpleName().equals(type)) + { + return new TrustStoreRecoverer(); + } + else if(PreferencesProvider.class.getSimpleName().equals(type)) + { + return new PreferencesProviderRecoverer(_preferencesProviderCreator); + } + else if(Plugin.class.getSimpleName().equals(type)) + { + return new PluginRecoverer(_pluginFactoryServiceLoader); + } + + return null; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/GroupProviderRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/GroupProviderRecoverer.java new file mode 100644 index 0000000000..00f23b3c1c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/GroupProviderRecoverer.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.configuration.startup; + +import java.util.Map; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.model.adapter.GroupProviderFactory; + +public class GroupProviderRecoverer implements ConfiguredObjectRecoverer +{ + private GroupProviderFactory _groupProviderFactory; + + public GroupProviderRecoverer(GroupProviderFactory groupProviderFactory) + { + super(); + _groupProviderFactory = groupProviderFactory; + } + + @Override + public GroupProvider create(RecovererProvider recovererProvider, ConfigurationEntry configurationEntry, ConfiguredObject... parents) + { + Broker broker = RecovererHelper.verifyOnlyBrokerIsParent(parents); + Map attributes = configurationEntry.getAttributes(); + + GroupProvider groupProvider = _groupProviderFactory.recover(configurationEntry.getId(), broker, attributes); + + return groupProvider; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/KeyStoreRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/KeyStoreRecoverer.java new file mode 100644 index 0000000000..8efedd37b5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/KeyStoreRecoverer.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.configuration.startup; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.adapter.KeyStoreAdapter; + +public class KeyStoreRecoverer implements ConfiguredObjectRecoverer +{ + @Override + public KeyStore create(RecovererProvider recovererProvider, ConfigurationEntry entry, ConfiguredObject... parents) + { + Broker broker = RecovererHelper.verifyOnlyBrokerIsParent(parents); + return new KeyStoreAdapter(entry.getId(), broker, entry.getAttributes()); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PluginRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PluginRecoverer.java new file mode 100644 index 0000000000..ddc4482953 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PluginRecoverer.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.configuration.startup; + +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.plugin.PluginFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; + +public class PluginRecoverer implements ConfiguredObjectRecoverer +{ + private QpidServiceLoader _serviceLoader; + + public PluginRecoverer(QpidServiceLoader serviceLoader) + { + _serviceLoader = serviceLoader; + } + + @Override + public ConfiguredObject create(RecovererProvider recovererProvider, ConfigurationEntry configurationEntry, ConfiguredObject... parents) + { + Broker broker = RecovererHelper.verifyOnlyBrokerIsParent(parents); + Map attributes = configurationEntry.getAttributes(); + Iterable factories = _serviceLoader.instancesOf(PluginFactory.class); + for (PluginFactory pluginFactory : factories) + { + UUID configurationId = configurationEntry.getId(); + ConfiguredObject pluginObject = pluginFactory.createInstance(configurationId, attributes, broker); + if (pluginObject != null) + { + UUID pluginId = pluginObject.getId(); + if (!configurationId.equals(pluginId)) + { + throw new IllegalStateException("Plugin object id '" + pluginId + "' does not equal expected id " + configurationId); + } + return pluginObject; + } + } + throw new IllegalConfigurationException("Cannot create a plugin object for " + attributes + " with factories " + factories); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PortRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PortRecoverer.java new file mode 100644 index 0000000000..147e835a8d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PortRecoverer.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.configuration.startup; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.adapter.BrokerAdapter; +import org.apache.qpid.server.model.adapter.PortFactory; + +public class PortRecoverer implements ConfiguredObjectRecoverer +{ + /** + * delegates to a {@link PortFactory} so that the logic can be shared by + * {@link BrokerAdapter} + */ + private final PortFactory _portFactory; + + public PortRecoverer(PortFactory portFactory) + { + _portFactory = portFactory; + } + + @Override + public Port create(RecovererProvider recovererProvider, ConfigurationEntry configurationEntry, ConfiguredObject... parents) + { + Broker broker = RecovererHelper.verifyOnlyBrokerIsParent(parents); + return _portFactory.createPort(configurationEntry.getId(), broker, configurationEntry.getAttributes()); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PreferencesProviderRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PreferencesProviderRecoverer.java new file mode 100644 index 0000000000..a455a61fee --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/PreferencesProviderRecoverer.java @@ -0,0 +1,41 @@ +package org.apache.qpid.server.configuration.startup; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.model.adapter.PreferencesProviderCreator; + +public class PreferencesProviderRecoverer implements ConfiguredObjectRecoverer +{ + + private PreferencesProviderCreator _preferencesProviderCreator; + + public PreferencesProviderRecoverer(PreferencesProviderCreator preferencesProviderCreator) + { + _preferencesProviderCreator = preferencesProviderCreator; + } + + @Override + public PreferencesProvider create(RecovererProvider recovererProvider, ConfigurationEntry entry, + ConfiguredObject... parents) + { + if (parents == null || parents.length == 0) + { + throw new IllegalArgumentException("AuthenticationProvider parent is not passed!"); + } + if (parents.length != 1) + { + throw new IllegalArgumentException("Only one parent is expected!"); + } + if (!(parents[0] instanceof AuthenticationProvider)) + { + throw new IllegalArgumentException("Parent is not a AuthenticationProvider"); + } + AuthenticationProvider authenticationProvider = (AuthenticationProvider)parents[0]; + return _preferencesProviderCreator.recover(entry.getId(), entry.getAttributes(), authenticationProvider); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/RecovererHelper.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/RecovererHelper.java new file mode 100644 index 0000000000..b60c9c289f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/RecovererHelper.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.configuration.startup; + +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; + +public class RecovererHelper +{ + public static Broker verifyOnlyBrokerIsParent(ConfiguredObject... parents) + { + if (parents == null || parents.length == 0) + { + throw new IllegalArgumentException("Broker parent is not passed!"); + } + if (parents.length != 1) + { + throw new IllegalArgumentException("Only one parent is expected!"); + } + if (!(parents[0] instanceof Broker)) + { + throw new IllegalArgumentException("Parent is not a broker"); + } + return (Broker)parents[0]; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/StoreUpgrader.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/StoreUpgrader.java new file mode 100644 index 0000000000..0789664dd8 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/StoreUpgrader.java @@ -0,0 +1,86 @@ +package org.apache.qpid.server.configuration.startup;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.model.Broker; + +public abstract class StoreUpgrader +{ + + private static Map _upgraders = new HashMap(); + + // Note: don't use externally defined constants in upgraders in case they change, the values here MUST stay the same + // no matter what changes are made to the code in the future + + private final static StoreUpgrader UPGRADE_1_0 = new StoreUpgrader("1.0") + { + @Override + protected void doUpgrade(ConfigurationEntryStore store) + { + ConfigurationEntry root = store.getRootEntry(); + Map> children = root.getChildren(); + Collection vhosts = children.get("VirtualHost"); + Collection changed = new HashSet(); + for(ConfigurationEntry vhost : vhosts) + { + Map attributes = vhost.getAttributes(); + if(attributes.containsKey("storeType")) + { + attributes = new HashMap(attributes); + attributes.put("type", "STANDARD"); + + changed.add(new ConfigurationEntry(vhost.getId(),vhost.getType(),attributes,vhost.getChildrenIds(),store)); + + } + + } + Map attributes = new HashMap(root.getAttributes()); + attributes.put(Broker.MODEL_VERSION, "1.1"); + changed.add(new ConfigurationEntry(root.getId(),root.getType(),attributes,root.getChildrenIds(),store)); + + store.save(changed.toArray(new ConfigurationEntry[changed.size()])); + + } + }; + + private StoreUpgrader(String version) + { + _upgraders.put(version, this); + } + + public static void upgrade(ConfigurationEntryStore store) + { + StoreUpgrader upgrader = _upgraders.get(store.getRootEntry().getAttributes().get(Broker.MODEL_VERSION).toString()); + if(upgrader != null) + { + upgrader.doUpgrade(store); + } + } + + protected abstract void doUpgrade(ConfigurationEntryStore store); + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/TrustStoreRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/TrustStoreRecoverer.java new file mode 100644 index 0000000000..7e9428a4d6 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/TrustStoreRecoverer.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.configuration.startup; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.model.adapter.TrustStoreAdapter; + +public class TrustStoreRecoverer implements ConfiguredObjectRecoverer +{ + @Override + public TrustStore create(RecovererProvider recovererProvider, ConfigurationEntry entry, ConfiguredObject... parents) + { + Broker broker = RecovererHelper.verifyOnlyBrokerIsParent(parents); + return new TrustStoreAdapter(entry.getId(), broker, entry.getAttributes()); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/VirtualHostRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/VirtualHostRecoverer.java new file mode 100644 index 0000000000..4f863adfb5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/startup/VirtualHostRecoverer.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.configuration.startup; + + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.adapter.VirtualHostAdapter; +import org.apache.qpid.server.stats.StatisticsGatherer; + +public class VirtualHostRecoverer implements ConfiguredObjectRecoverer +{ + private StatisticsGatherer _brokerStatisticsGatherer; + + public VirtualHostRecoverer(StatisticsGatherer brokerStatisticsGatherer) + { + super(); + _brokerStatisticsGatherer = brokerStatisticsGatherer; + } + + @Override + public VirtualHost create(RecovererProvider recovererProvider, ConfigurationEntry entry, ConfiguredObject... parents) + { + Broker broker = RecovererHelper.verifyOnlyBrokerIsParent(parents); + + return new VirtualHostAdapter(entry.getId(), entry.getAttributes(), broker, _brokerStatisticsGatherer, broker.getTaskExecutor()); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreUtil.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreUtil.java new file mode 100644 index 0000000000..6d895892b3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreUtil.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.configuration.store; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.util.FileUtils; + +public class ConfigurationEntryStoreUtil +{ + public void copyInitialConfigFile(String initialConfigLocation, File destinationFile) + { + URL initialStoreURL = toURL(initialConfigLocation); + InputStream in = null; + try + { + in = initialStoreURL.openStream(); + FileUtils.copy(in, destinationFile); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot create file " + destinationFile + " by copying initial config from " + initialConfigLocation , e); + } + finally + { + if (in != null) + { + try + { + in.close(); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot close initial config input stream: " + initialConfigLocation , e); + } + } + } + } + + public URL toURL(String location) + { + URL url = null; + try + { + url = new URL(location); + } + catch (MalformedURLException e) + { + File locationFile = new File(location); + url = fileToURL(locationFile); + } + return url; + } + + protected URL fileToURL(File storeFile) + { + URL storeURL = null; + try + { + storeURL = storeFile.toURI().toURL(); + } + catch (MalformedURLException e) + { + throw new IllegalConfigurationException("Cannot create URL for file " + storeFile, e); + } + return storeURL; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStore.java new file mode 100644 index 0000000000..df82822a86 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStore.java @@ -0,0 +1,128 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.store; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.IllegalConfigurationException; + +public class JsonConfigurationEntryStore extends MemoryConfigurationEntryStore +{ + public static final String STORE_TYPE = "json"; + + private File _storeFile; + + public JsonConfigurationEntryStore(String storeLocation, ConfigurationEntryStore initialStore, boolean overwrite, Map configProperties) + { + super(configProperties); + _storeFile = new File(storeLocation); + + if(_storeFile.isDirectory()) + { + throw new IllegalConfigurationException("A directory exists at the location for the broker configuration store file: " + storeLocation); + } + + if(overwrite && _storeFile.exists()) + { + if(!_storeFile.delete()) + { + throw new RuntimeException("Unable to overwrite existing configuration store file as requested: " + storeLocation); + } + } + + if ((!_storeFile.exists() || _storeFile.length() == 0)) + { + initialiseStore(_storeFile, initialStore); + } + load(getConfigurationEntryStoreUtil().fileToURL(_storeFile)); + if(isGeneratedObjectIdDuringLoad()) + { + saveAsTree(_storeFile); + } + } + + @Override + public synchronized UUID[] remove(UUID... entryIds) + { + UUID[] removedIds = super.remove(entryIds); + if (removedIds.length > 0) + { + saveAsTree(_storeFile); + } + return removedIds; + } + + @Override + public synchronized void save(ConfigurationEntry... entries) + { + if (replaceEntries(entries)) + { + saveAsTree(_storeFile); + } + } + + @Override + public String getStoreLocation() + { + return _storeFile.getAbsolutePath(); + } + + @Override + public String getType() + { + return STORE_TYPE; + } + + @Override + public String toString() + { + return "JsonConfigurationEntryStore [_storeFile=" + _storeFile + ", _rootId=" + getRootEntry().getId() + "]"; + } + + private void initialiseStore(File storeFile, ConfigurationEntryStore initialStore) + { + createFileIfNotExist(storeFile); + if (initialStore == null) + { + throw new IllegalConfigurationException("Cannot create new store without an initial store"); + } + else + { + if (initialStore instanceof MemoryConfigurationEntryStore && initialStore.getStoreLocation() != null) + { + getConfigurationEntryStoreUtil().copyInitialConfigFile(initialStore.getStoreLocation(), storeFile); + } + else + { + ConfigurationEntry rootEntry = initialStore.getRootEntry(); + Map entries = new HashMap(); + copyEntry(rootEntry.getId(), initialStore, entries); + saveAsTree(rootEntry.getId(), entries, getObjectMapper(), storeFile, getVersion()); + } + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java new file mode 100644 index 0000000000..639f3cd5c4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandler.java @@ -0,0 +1,353 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.store; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Protocol; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.util.MapValueConverter; + +public class ManagementModeStoreHandler implements ConfigurationEntryStore +{ + private static final Logger LOGGER = Logger.getLogger(ManagementModeStoreHandler.class); + + private static final String MANAGEMENT_MODE_PORT_PREFIX = "MANAGEMENT-MODE-PORT-"; + private static final String PORT_TYPE = Port.class.getSimpleName(); + private static final String VIRTUAL_HOST_TYPE = VirtualHost.class.getSimpleName(); + private static final String ATTRIBUTE_STATE = VirtualHost.STATE; + private static final Object MANAGEMENT_MODE_AUTH_PROVIDER = "mm-auth"; + + + private final ConfigurationEntryStore _store; + private final Map _cliEntries; + private final Map _quiescedEntries; + private final UUID _rootId; + + public ManagementModeStoreHandler(ConfigurationEntryStore store, BrokerOptions options) + { + ConfigurationEntry storeRoot = store.getRootEntry(); + _store = store; + _rootId = storeRoot.getId(); + _cliEntries = createPortsFromCommadLineOptions(options); + _quiescedEntries = quiesceEntries(storeRoot, options); + } + + @Override + public ConfigurationEntry getRootEntry() + { + return getEntry(_rootId); + } + + @Override + public ConfigurationEntry getEntry(UUID id) + { + synchronized (_store) + { + if (_cliEntries.containsKey(id)) + { + return _cliEntries.get(id); + } + + ConfigurationEntry entry = _store.getEntry(id); + if (_quiescedEntries.containsKey(id)) + { + entry = createEntryWithState(entry, State.QUIESCED); + } + else if (id == _rootId) + { + entry = createRootWithCLIEntries(entry); + } + return entry; + } + } + + @Override + public void save(ConfigurationEntry... entries) + { + synchronized (_store) + { + ConfigurationEntry[] entriesToSave = new ConfigurationEntry[entries.length]; + + for (int i = 0; i < entries.length; i++) + { + ConfigurationEntry entry = entries[i]; + UUID id = entry.getId(); + if (_cliEntries.containsKey(id)) + { + throw new IllegalConfigurationException("Cannot save configuration provided as command line argument:" + + entry); + } + else if (_quiescedEntries.containsKey(id)) + { + // save entry with the original state + entry = createEntryWithState(entry, _quiescedEntries.get(ATTRIBUTE_STATE)); + } + else if (_rootId.equals(id)) + { + // save root without command line entries + Set childrenIds = new HashSet(entry.getChildrenIds()); + if (!_cliEntries.isEmpty()) + { + childrenIds.removeAll(_cliEntries.entrySet()); + } + HashMap attributes = new HashMap(entry.getAttributes()); + entry = new ConfigurationEntry(entry.getId(), entry.getType(), attributes, childrenIds, this); + } + entriesToSave[i] = entry; + } + + _store.save(entriesToSave); + } + } + + @Override + public UUID[] remove(UUID... entryIds) + { + synchronized (_store) + { + for (UUID id : entryIds) + { + if (_cliEntries.containsKey(id)) + { + throw new IllegalConfigurationException("Cannot change configuration for command line entry:" + + _cliEntries.get(id)); + } + } + UUID[] result = _store.remove(entryIds); + for (UUID id : entryIds) + { + if (_quiescedEntries.containsKey(id)) + { + _quiescedEntries.remove(id); + } + } + return result; + } + } + + @Override + public void copyTo(String copyLocation) + { + synchronized (_store) + { + _store.copyTo(copyLocation); + } + } + + @Override + public String getStoreLocation() + { + return _store.getStoreLocation(); + } + + @Override + public int getVersion() + { + return _store.getVersion(); + } + + @Override + public String getType() + { + return _store.getType(); + } + + private Map createPortsFromCommadLineOptions(BrokerOptions options) + { + int managementModeRmiPortOverride = options.getManagementModeRmiPortOverride(); + if (managementModeRmiPortOverride < 0) + { + throw new IllegalConfigurationException("Invalid rmi port is specified: " + managementModeRmiPortOverride); + } + int managementModeJmxPortOverride = options.getManagementModeJmxPortOverride(); + if (managementModeJmxPortOverride < 0) + { + throw new IllegalConfigurationException("Invalid jmx port is specified: " + managementModeJmxPortOverride); + } + int managementModeHttpPortOverride = options.getManagementModeHttpPortOverride(); + if (managementModeHttpPortOverride < 0) + { + throw new IllegalConfigurationException("Invalid http port is specified: " + managementModeHttpPortOverride); + } + Map cliEntries = new HashMap(); + if (managementModeRmiPortOverride != 0) + { + ConfigurationEntry entry = createCLIPortEntry(managementModeRmiPortOverride, Protocol.RMI); + cliEntries.put(entry.getId(), entry); + if (managementModeJmxPortOverride == 0) + { + ConfigurationEntry connectorEntry = createCLIPortEntry(managementModeRmiPortOverride + 100, Protocol.JMX_RMI); + cliEntries.put(connectorEntry.getId(), connectorEntry); + } + } + if (managementModeJmxPortOverride != 0) + { + ConfigurationEntry entry = createCLIPortEntry(managementModeJmxPortOverride, Protocol.JMX_RMI); + cliEntries.put(entry.getId(), entry); + } + if (managementModeHttpPortOverride != 0) + { + ConfigurationEntry entry = createCLIPortEntry(managementModeHttpPortOverride, Protocol.HTTP); + cliEntries.put(entry.getId(), entry); + } + return cliEntries; + } + + private ConfigurationEntry createCLIPortEntry(int port, Protocol protocol) + { + Map attributes = new HashMap(); + attributes.put(Port.PORT, port); + attributes.put(Port.PROTOCOLS, Collections.singleton(protocol)); + attributes.put(Port.NAME, MANAGEMENT_MODE_PORT_PREFIX + protocol.name()); + if (protocol != Protocol.RMI) + { + attributes.put(Port.AUTHENTICATION_PROVIDER, MANAGEMENT_MODE_AUTH_PROVIDER); + } + ConfigurationEntry portEntry = new ConfigurationEntry(UUID.randomUUID(), PORT_TYPE, attributes, + Collections. emptySet(), this); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Add management mode port configuration " + portEntry + " for port " + port + " and protocol " + + protocol); + } + return portEntry; + } + + private ConfigurationEntry createRootWithCLIEntries(ConfigurationEntry storeRoot) + { + Set childrenIds = new HashSet(storeRoot.getChildrenIds()); + if (!_cliEntries.isEmpty()) + { + childrenIds.addAll(_cliEntries.keySet()); + } + ConfigurationEntry root = new ConfigurationEntry(storeRoot.getId(), storeRoot.getType(), new HashMap( + storeRoot.getAttributes()), childrenIds, this); + return root; + } + + private Map quiesceEntries(ConfigurationEntry storeRoot, BrokerOptions options) + { + Map quiescedEntries = new HashMap(); + Set childrenIds; + int managementModeRmiPortOverride = options.getManagementModeRmiPortOverride(); + int managementModeJmxPortOverride = options.getManagementModeJmxPortOverride(); + int managementModeHttpPortOverride = options.getManagementModeHttpPortOverride(); + childrenIds = storeRoot.getChildrenIds(); + for (UUID id : childrenIds) + { + ConfigurationEntry entry = _store.getEntry(id); + String entryType = entry.getType(); + Map attributes = entry.getAttributes(); + boolean quiesce = false; + if (VIRTUAL_HOST_TYPE.equals(entryType) && options.isManagementModeQuiesceVirtualHosts()) + { + quiesce = true; + } + else if (PORT_TYPE.equals(entryType)) + { + if (attributes == null) + { + throw new IllegalConfigurationException("Port attributes are not set in " + entry); + } + Set protocols = getPortProtocolsAttribute(attributes); + if (protocols == null) + { + quiesce = true; + } + else + { + for (Protocol protocol : protocols) + { + switch (protocol) + { + case JMX_RMI: + quiesce = managementModeJmxPortOverride > 0 || managementModeRmiPortOverride > 0; + break; + case RMI: + quiesce = managementModeRmiPortOverride > 0; + break; + case HTTP: + quiesce = managementModeHttpPortOverride > 0; + break; + default: + quiesce = true; + } + } + } + } + if (quiesce) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Management mode quiescing entry " + entry); + } + + // save original state + quiescedEntries.put(entry.getId(), attributes.get(ATTRIBUTE_STATE)); + } + } + return quiescedEntries; + } + + private Set getPortProtocolsAttribute(Map attributes) + { + Object object = attributes.get(Port.PROTOCOLS); + if (object == null) + { + return null; + } + return MapValueConverter.getEnumSetAttribute(Port.PROTOCOLS, attributes, Protocol.class); + } + + private ConfigurationEntry createEntryWithState(ConfigurationEntry entry, Object state) + { + Map attributes = new HashMap(entry.getAttributes()); + if (state == null) + { + attributes.remove(ATTRIBUTE_STATE); + } + else + { + attributes.put(ATTRIBUTE_STATE, state); + } + Set originalChildren = entry.getChildrenIds(); + Set children = null; + if (originalChildren != null) + { + children = new HashSet(originalChildren); + } + return new ConfigurationEntry(entry.getId(), entry.getType(), attributes, children, entry.getStore()); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java new file mode 100644 index 0000000000..92d170fad9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStore.java @@ -0,0 +1,709 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.store; + +import static org.apache.qpid.server.configuration.ConfigurationEntry.ATTRIBUTE_NAME; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +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.TreeMap; +import java.util.TreeSet; +import java.util.UUID; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Model; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.util.Strings; +import org.apache.qpid.util.Strings.ChainedResolver; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; +import org.codehaus.jackson.node.ArrayNode; + +public class MemoryConfigurationEntryStore implements ConfigurationEntryStore +{ + public static final String STORE_TYPE = "memory"; + + private static final String DEFAULT_BROKER_NAME = "Broker"; + private static final String ID = "id"; + private static final String TYPE = "@type"; + + static final int STORE_VERSION = 1; + + private final ObjectMapper _objectMapper; + private final Map _entries; + private final Map> _relationshipClasses; + private final ConfigurationEntryStoreUtil _util = new ConfigurationEntryStoreUtil(); + + private String _storeLocation; + private UUID _rootId; + + private boolean _generatedObjectIdDuringLoad; + + private ChainedResolver _resolver; + + protected MemoryConfigurationEntryStore(Map configProperties) + { + _objectMapper = new ObjectMapper(); + _objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + _objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true); + _entries = new HashMap(); + _relationshipClasses = buildRelationshipClassMap(); + _resolver = new Strings.ChainedResolver(Strings.SYSTEM_RESOLVER, + new Strings.MapResolver(configProperties)); + } + + MemoryConfigurationEntryStore(String json, Map configProperties) + { + this(configProperties); + if (json == null || "".equals(json)) + { + createRootEntry(); + } + else + { + loadFromJson(json); + } + } + + public MemoryConfigurationEntryStore(String initialStoreLocation, ConfigurationEntryStore initialStore, Map configProperties) + { + this(configProperties); + if (initialStore == null && (initialStoreLocation == null || "".equals(initialStoreLocation) )) + { + throw new IllegalConfigurationException("Cannot instantiate the memory broker store as neither initial store nor initial store location is provided"); + } + + if (initialStore != null) + { + if (initialStore instanceof MemoryConfigurationEntryStore) + { + _storeLocation = initialStore.getStoreLocation(); + } + _rootId = initialStore.getRootEntry().getId(); + copyEntry(_rootId, initialStore, _entries); + } + else + { + _storeLocation = initialStoreLocation; + load(_util.toURL(_storeLocation)); + } + } + + @Override + public synchronized UUID[] remove(UUID... entryIds) + { + List removedIds = new ArrayList(); + for (UUID uuid : entryIds) + { + if (_rootId.equals(uuid)) + { + throw new IllegalConfigurationException("Cannot remove root entry"); + } + } + for (UUID uuid : entryIds) + { + if (removeInternal(uuid)) + { + // remove references to the entry from parent entries + for (ConfigurationEntry entry : _entries.values()) + { + if (entry.hasChild(uuid)) + { + Set children = new HashSet(entry.getChildrenIds()); + children.remove(uuid); + ConfigurationEntry referal = new ConfigurationEntry(entry.getId(), entry.getType(), + entry.getAttributes(), children, this); + _entries.put(entry.getId(), referal); + } + } + removedIds.add(uuid); + } + } + + return removedIds.toArray(new UUID[removedIds.size()]); + } + + @Override + public synchronized void save(ConfigurationEntry... entries) + { + replaceEntries(entries); + } + + @Override + public ConfigurationEntry getRootEntry() + { + return getEntry(_rootId); + } + + @Override + public synchronized ConfigurationEntry getEntry(UUID id) + { + return _entries.get(id); + } + + /** + * Copies the store into the given location + * + * @param target location to copy store into + * @throws IllegalConfigurationException if store cannot be copied into given location + */ + public void copyTo(String copyLocation) + { + File file = new File(copyLocation); + if (!file.exists()) + { + createFileIfNotExist(file); + } + saveAsTree(file); + } + + @Override + public String getStoreLocation() + { + return _storeLocation; + } + + @Override + public int getVersion() + { + return STORE_VERSION; + } + + @Override + public String getType() + { + return STORE_TYPE; + } + + @Override + public String toString() + { + return "MemoryConfigurationEntryStore [_rootId=" + _rootId + "]"; + } + + protected boolean replaceEntries(ConfigurationEntry... entries) + { + boolean anySaved = false; + for (ConfigurationEntry entry : entries) + { + ConfigurationEntry oldEntry = _entries.put(entry.getId(), entry); + if (!entry.equals(oldEntry)) + { + anySaved = true; + } + } + return anySaved; + } + + protected ObjectMapper getObjectMapper() + { + return _objectMapper; + } + + protected void saveAsTree(File file) + { + saveAsTree(_rootId, _entries, _objectMapper, file, STORE_VERSION); + } + + protected void saveAsTree(UUID rootId, Map entries, ObjectMapper mapper, File file, int version) + { + Map tree = toTree(rootId, entries); + tree.put(Broker.STORE_VERSION, version); + try + { + mapper.writeValue(file, tree); + } + catch (JsonGenerationException e) + { + throw new IllegalConfigurationException("Cannot generate json!", e); + } + catch (JsonMappingException e) + { + throw new IllegalConfigurationException("Cannot map objects for json serialization!", e); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot save configuration into " + file + "!", e); + } + } + + protected void load(URL url) + { + InputStream is = null; + try + { + is = url.openStream(); + JsonNode node = loadJsonNodes(is, _objectMapper); + + int storeVersion = 0; + JsonNode storeVersionNode = node.get(Broker.STORE_VERSION); + if (storeVersionNode == null || storeVersionNode.isNull()) + { + throw new IllegalConfigurationException("Broker " + Broker.STORE_VERSION + " attribute must be specified"); + } + else + { + storeVersion = storeVersionNode.getIntValue(); + } + + if (storeVersion != STORE_VERSION) + { + throw new IllegalConfigurationException("The data of version " + storeVersion + + " can not be loaded by store of version " + STORE_VERSION); + } + + ConfigurationEntry brokerEntry = toEntry(node, Broker.class, _entries, null); + _rootId = brokerEntry.getId(); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot load store from: " + url, e); + } + finally + { + if (is != null) + { + if (is != null) + { + try + { + is.close(); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot close input stream for: " + url, e); + } + } + } + } + } + + protected void createFileIfNotExist(File file) + { + File parent = file.getParentFile(); + if (!parent.exists()) + { + if (!parent.mkdirs()) + { + throw new IllegalConfigurationException("Cannot create folders " + parent); + } + } + try + { + file.createNewFile(); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot create file " + file, e); + } + } + + protected void copyEntry(UUID entryId, ConfigurationEntryStore initialStore, Map entries) + { + ConfigurationEntry entry = initialStore.getEntry(entryId); + if (entry != null) + { + if (entries.containsKey(entryId)) + { + throw new IllegalConfigurationException("Duplicate id is found: " + entryId + + "! The following configuration entries have the same id: " + entries.get(entryId) + ", " + entry); + } + + Set children = entry.getChildrenIds(); + Set childrenCopy = children == null? null : new HashSet(children); + ConfigurationEntry copy = new ConfigurationEntry(entryId, entry.getType(), new HashMap(entry.getAttributes()), childrenCopy, this); + entries.put(entryId, copy); + if (children != null) + { + for (UUID uuid : children) + { + copyEntry(uuid, initialStore, entries); + } + } + } + } + + private void loadFromJson(String json) + { + ByteArrayInputStream bais = null; + try + { + byte[] bytes = json.getBytes("UTF-8"); + bais = new ByteArrayInputStream(bytes); + JsonNode node = loadJsonNodes(bais, _objectMapper); + ConfigurationEntry brokerEntry = toEntry(node, Broker.class, _entries, null); + _rootId = brokerEntry.getId(); + } + catch(Exception e) + { + throw new IllegalConfigurationException("Cannot create store from json:" + json); + } + finally + { + if (bais != null) + { + try + { + bais.close(); + } + catch (IOException e) + { + // ByteArrayInputStream#close() is an empty method + } + } + } + } + + private void createRootEntry() + { + ConfigurationEntry brokerEntry = new ConfigurationEntry(UUIDGenerator.generateRandomUUID(), + Broker.class.getSimpleName(), Collections. emptyMap(), Collections. emptySet(), this); + _rootId = brokerEntry.getId(); + _entries.put(_rootId, brokerEntry); + } + + private Map toTree(UUID rootId, Map entries) + { + ConfigurationEntry entry = entries.get(rootId); + if (entry == null || !entry.getId().equals(rootId)) + { + throw new IllegalConfigurationException("Cannot find entry with id " + rootId + "!"); + } + Map tree = new TreeMap(); + Map attributes = entry.getAttributes(); + if (attributes != null) + { + tree.putAll(attributes); + } + tree.put(ID, entry.getId()); + Set childrenIds = entry.getChildrenIds(); + if (childrenIds != null && !childrenIds.isEmpty()) + { + for (UUID relationship : childrenIds) + { + ConfigurationEntry child = entries.get(relationship); + if (child != null) + { + String relationshipName = child.getType().toLowerCase() + "s"; + + @SuppressWarnings("unchecked") + Collection> children = (Collection>) tree.get(relationshipName); + if (children == null) + { + children = new ArrayList>(); + tree.put(relationshipName, children); + } + Map childAsMap = toTree(relationship, entries); + children.add(childAsMap); + } + } + } + return tree; + } + + private Map> buildRelationshipClassMap() + { + Map> relationships = new HashMap>(); + + Collection> children = Model.getInstance().getChildTypes(Broker.class); + for (Class childClass : children) + { + String name = childClass.getSimpleName().toLowerCase(); + String relationshipName = name + (name.endsWith("s") ? "es" : "s"); + relationships.put(relationshipName, childClass); + } + return relationships; + } + + private boolean removeInternal(UUID entryId) + { + ConfigurationEntry oldEntry = _entries.remove(entryId); + if (oldEntry != null) + { + Set children = oldEntry.getChildrenIds(); + if (children != null && !children.isEmpty()) + { + for (UUID childId : children) + { + removeInternal(childId); + } + } + return true; + } + return false; + } + + private JsonNode loadJsonNodes(InputStream is, ObjectMapper mapper) + { + JsonNode root = null; + try + { + root = mapper.readTree(is); + } + catch (JsonProcessingException e) + { + throw new IllegalConfigurationException("Cannot parse json", e); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot read json", e); + } + return root; + } + + private ConfigurationEntry toEntry(JsonNode parent, Class expectedConfiguredObjectClass, Map entries, Class parentClass) + { + Map attributes = null; + Set childrenIds = new TreeSet(); + Iterator fieldNames = parent.getFieldNames(); + String type = null; + String idAsString = null; + while (fieldNames.hasNext()) + { + String fieldName = fieldNames.next(); + JsonNode fieldNode = parent.get(fieldName); + if (fieldName.equals(ID)) + { + idAsString = fieldNode.asText(); + } + else if (fieldName.equals(TYPE)) + { + type = fieldNode.asText(); + } + else if (fieldNode.isArray()) + { + // array containing either broker children or attribute values + Iterator elements = fieldNode.getElements(); + List fieldValues = null; + while (elements.hasNext()) + { + JsonNode element = elements.next(); + if (element.isObject()) + { + Class expectedChildConfiguredObjectClass = _relationshipClasses.get(fieldName); + if (expectedChildConfiguredObjectClass == null && expectedConfiguredObjectClass != null) + { + Collection> childTypes = Model.getInstance().getChildTypes(expectedConfiguredObjectClass); + for (Class childType : childTypes) + { + String relationship = childType.getSimpleName().toLowerCase(); + relationship += relationship.endsWith("s") ? "es": "s"; + if (fieldName.equals(relationship)) + { + expectedChildConfiguredObjectClass = childType; + break; + } + } + } + // assuming it is a child node + ConfigurationEntry entry = toEntry(element, expectedChildConfiguredObjectClass, entries, expectedConfiguredObjectClass); + childrenIds.add(entry.getId()); + } + else + { + if (fieldValues == null) + { + fieldValues = new ArrayList(); + } + fieldValues.add(toObject(element)); + } + } + if (fieldValues != null) + { + Object[] array = fieldValues.toArray(new Object[fieldValues.size()]); + if (attributes == null) + { + attributes = new HashMap(); + } + attributes.put(fieldName, array); + } + } + else if (fieldNode.isObject()) + { + // ignore, in-line objects are not supported yet + } + else + { + // primitive attribute + Object value = toObject(fieldNode); + if (attributes == null) + { + attributes = new HashMap(); + } + attributes.put(fieldName, value); + } + } + + if (type == null) + { + if (expectedConfiguredObjectClass == null) + { + throw new IllegalConfigurationException("Type attribute is not provided for configuration entry " + parent); + } + else + { + type = expectedConfiguredObjectClass.getSimpleName(); + } + } + String name = null; + if (attributes != null) + { + name = (String) attributes.get(ATTRIBUTE_NAME); + } + if ((name == null || "".equals(name))) + { + if (expectedConfiguredObjectClass == Broker.class) + { + name = DEFAULT_BROKER_NAME; + } + else + { + throw new IllegalConfigurationException("Name attribute is not provided for configuration entry " + parent); + } + } + UUID id = null; + if (idAsString == null) + { + id = UUIDGenerator.generateRandomUUID(); + + _generatedObjectIdDuringLoad = true; + } + else + { + try + { + id = UUID.fromString(idAsString); + } + catch (Exception e) + { + throw new IllegalConfigurationException( + "ID attribute value does not conform to UUID format for configuration entry " + parent); + } + } + ConfigurationEntry entry = new ConfigurationEntry(id, type, attributes, childrenIds, this); + if (entries.containsKey(id)) + { + throw new IllegalConfigurationException("Duplicate id is found: " + id + + "! The following configuration entries have the same id: " + entries.get(id) + ", " + entry); + } + entries.put(id, entry); + return entry; + } + + private Object toObject(JsonNode node) + { + if (node.isValueNode()) + { + if (node.isBoolean()) + { + return node.asBoolean(); + } + else if (node.isDouble()) + { + return node.asDouble(); + } + else if (node.isInt()) + { + return node.asInt(); + } + else if (node.isLong()) + { + return node.asLong(); + } + else if (node.isNull()) + { + return null; + } + else + { + return Strings.expand(node.asText(), _resolver); + } + } + else if (node.isArray()) + { + return toArray(node); + } + else if (node.isObject()) + { + return toMap(node); + } + else + { + throw new IllegalConfigurationException("Unexpected node: " + node); + } + } + + private Map toMap(JsonNode node) + { + Map object = new TreeMap(); + Iterator fieldNames = node.getFieldNames(); + while (fieldNames.hasNext()) + { + String name = fieldNames.next(); + Object value = toObject(node.get(name)); + object.put(name, value); + } + return object; + } + + private Object toArray(JsonNode node) + { + ArrayNode arrayNode = (ArrayNode) node; + Object[] array = new Object[arrayNode.size()]; + Iterator elements = arrayNode.getElements(); + for (int i = 0; i < array.length; i++) + { + array[i] = toObject(elements.next()); + } + return array; + } + + protected boolean isGeneratedObjectIdDuringLoad() + { + return _generatedObjectIdDuringLoad; + } + + protected ConfigurationEntryStoreUtil getConfigurationEntryStoreUtil() + { + return _util; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListener.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListener.java new file mode 100644 index 0000000000..3022898300 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListener.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.configuration.store; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfigurationChangeListener; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Model; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; + +public class StoreConfigurationChangeListener implements ConfigurationChangeListener +{ + private ConfigurationEntryStore _store; + + public StoreConfigurationChangeListener(ConfigurationEntryStore store) + { + super(); + _store = store; + } + + @Override + public void stateChanged(ConfiguredObject object, State oldState, State newState) + { + if (newState == State.DELETED) + { + _store.remove(object.getId()); + object.removeChangeListener(this); + } + } + + @Override + public void childAdded(ConfiguredObject object, ConfiguredObject child) + { + // exclude VirtualHost children from storing in broker store + if (!(object instanceof VirtualHost)) + { + child.addChangeListener(this); + ConfigurationEntry parentEntry = toConfigurationEntry(object); + ConfigurationEntry childEntry = toConfigurationEntry(child); + _store.save(parentEntry, childEntry); + } + + } + + @Override + public void childRemoved(ConfiguredObject object, ConfiguredObject child) + { + _store.save(toConfigurationEntry(object)); + } + + @Override + public void attributeSet(ConfiguredObject object, String attrinuteName, Object oldAttributeValue, Object newAttributeValue) + { + _store.save(toConfigurationEntry(object)); + } + + private ConfigurationEntry toConfigurationEntry(ConfiguredObject object) + { + Class objectType = getConfiguredObjectType(object); + Set childrenIds = getChildernIds(object, objectType); + ConfigurationEntry entry = new ConfigurationEntry(object.getId(), objectType.getSimpleName(), + object.getActualAttributes(), childrenIds, _store); + return entry; + } + + private Set getChildernIds(ConfiguredObject object, Class objectType) + { + // Virtual Host children's IDs should not be stored in broker store + if (object instanceof VirtualHost) + { + return Collections.emptySet(); + } + Set childrenIds = new TreeSet(); + Collection> childClasses = Model.getInstance().getChildTypes(objectType); + if (childClasses != null) + { + for (Class childClass : childClasses) + { + Collection children = object.getChildren(childClass); + if (children != null) + { + for (ConfiguredObject childObject : children) + { + childrenIds.add(childObject.getId()); + } + } + } + } + return childrenIds; + } + + private Class getConfiguredObjectType(ConfiguredObject object) + { + if (object instanceof Broker) + { + return Broker.class; + } + else if (object instanceof VirtualHost) + { + return VirtualHost.class; + } + else if (object instanceof Port) + { + return Port.class; + } + else if (object instanceof AuthenticationProvider) + { + return AuthenticationProvider.class; + } + return getConfiguredObjectTypeFromImplementedInterfaces(object.getClass()); + } + + @SuppressWarnings("unchecked") + private Class getConfiguredObjectTypeFromImplementedInterfaces(Class objectClass) + { + // get all implemented interfaces extending ConfiguredObject + Set> interfaces = getImplementedInterfacesExtendingSuper(objectClass, ConfiguredObject.class); + + if (interfaces.size() == 0) + { + throw new RuntimeException("Can not identify the configured object type"); + } + + if (interfaces.size() == 1) + { + return (Class)interfaces.iterator().next(); + } + + Set> superInterfaces = new HashSet>(); + + // find all super interfaces + for (Class interfaceClass : interfaces) + { + for (Class interfaceClass2 : interfaces) + { + if (interfaceClass != interfaceClass2) + { + if (interfaceClass.isAssignableFrom(interfaceClass2)) + { + superInterfaces.add(interfaceClass); + } + } + } + } + + // remove super interfaces + for (Class superInterface : superInterfaces) + { + interfaces.remove(superInterface); + } + + if (interfaces.size() == 1) + { + return (Class)interfaces.iterator().next(); + } + else + { + throw new RuntimeException("Can not identify the configured object type as an it implements" + + " more than one configured object interfaces: " + interfaces); + } + + } + + private Set> getImplementedInterfacesExtendingSuper(Class classInstance, Class superInterface) + { + Set> interfaces = new HashSet>(); + Class[] classInterfaces = classInstance.getInterfaces(); + for (Class interfaceClass : classInterfaces) + { + if (interfaceClass!= superInterface && superInterface.isAssignableFrom(interfaceClass)) + { + interfaces.add(interfaceClass); + } + } + Class superClass = classInstance.getSuperclass(); + if (superClass != null) + { + Set> superClassInterfaces = getImplementedInterfacesExtendingSuper(superClass, superInterface); + interfaces.addAll(superClassInterfaces); + } + return interfaces; + } + + @Override + public String toString() + { + return "StoreConfigurationChangeListener [store=" + _store + "]"; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/factory/JsonConfigurationStoreFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/factory/JsonConfigurationStoreFactory.java new file mode 100644 index 0000000000..de007e68d7 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/factory/JsonConfigurationStoreFactory.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.configuration.store.factory; + +import java.util.Map; + +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.store.JsonConfigurationEntryStore; +import org.apache.qpid.server.plugin.ConfigurationStoreFactory; + +public class JsonConfigurationStoreFactory implements ConfigurationStoreFactory +{ + @Override + public ConfigurationEntryStore createStore(String storeLocation, ConfigurationEntryStore initialStore, boolean overwrite, Map configProperties) + { + return new JsonConfigurationEntryStore(storeLocation, initialStore, overwrite, configProperties); + } + + @Override + public String getType() + { + return JsonConfigurationEntryStore.STORE_TYPE; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/factory/MemoryConfigurationStoreFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/factory/MemoryConfigurationStoreFactory.java new file mode 100644 index 0000000000..f7a9157144 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/store/factory/MemoryConfigurationStoreFactory.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.configuration.store.factory; + +import java.util.Map; + +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.store.MemoryConfigurationEntryStore; +import org.apache.qpid.server.plugin.ConfigurationStoreFactory; + +public class MemoryConfigurationStoreFactory implements ConfigurationStoreFactory +{ + @Override + public ConfigurationEntryStore createStore(String storeLocation, ConfigurationEntryStore initialStore, boolean overwrite, Map configProperties) + { + return new MemoryConfigurationEntryStore(null, initialStore, configProperties); + } + + @Override + public String getType() + { + return MemoryConfigurationEntryStore.STORE_TYPE; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/ChangeAttributesTask.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/ChangeAttributesTask.java new file mode 100644 index 0000000000..4ba4057fee --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/ChangeAttributesTask.java @@ -0,0 +1,62 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration.updater; + +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.qpid.server.model.ConfiguredObject; + +public class ChangeAttributesTask implements Callable +{ + private final Map _attributes; + private final ConfiguredObject _object; + + public ChangeAttributesTask(ConfiguredObject target, Map attributes) + { + super(); + _object = target; + _attributes = attributes; + } + + @Override + public Void call() throws Exception + { + _object.setAttributes(_attributes); + return null; + } + + public Map getAttributes() + { + return _attributes; + } + + public ConfiguredObject getObject() + { + return _object; + } + + @Override + public String toString() + { + return "ChangeAttributesTask [object=" + _object + ", attributes=" + _attributes + "]"; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/ChangeStateTask.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/ChangeStateTask.java new file mode 100644 index 0000000000..b6de1e136a --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/ChangeStateTask.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.configuration.updater; + +import java.util.concurrent.Callable; + +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.State; + +public final class ChangeStateTask implements Callable +{ + private ConfiguredObject _object; + private State _expectedState; + private State _desiredState; + + public ChangeStateTask(ConfiguredObject object, State expectedState, State desiredState) + { + _object = object; + _expectedState = expectedState; + _desiredState = desiredState; + } + + public ConfiguredObject getObject() + { + return _object; + } + + public State getExpectedState() + { + return _expectedState; + } + + public State getDesiredState() + { + return _desiredState; + } + + @Override + public State call() + { + return _object.setDesiredState(_expectedState, _desiredState); + } + + @Override + public String toString() + { + return "ChangeStateTask [object=" + _object + ", expectedState=" + _expectedState + ", desiredState=" + _desiredState + "]"; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/CreateChildTask.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/CreateChildTask.java new file mode 100644 index 0000000000..d3a8f5b797 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/CreateChildTask.java @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration.updater; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.apache.qpid.server.model.ConfiguredObject; + +public final class CreateChildTask implements Callable +{ + private ConfiguredObject _object; + private Class _childClass; + private Map _attributes; + private ConfiguredObject[] _otherParents; + + public CreateChildTask(ConfiguredObject object, Class childClass, Map attributes, + ConfiguredObject... otherParents) + { + _object = object; + _childClass = childClass; + _attributes = attributes; + _otherParents = otherParents; + } + + public ConfiguredObject getObject() + { + return _object; + } + + public Class getChildClass() + { + return _childClass; + } + + public Map getAttributes() + { + return _attributes; + } + + public ConfiguredObject[] getOtherParents() + { + return _otherParents; + } + + @Override + public ConfiguredObject call() + { + return _object.createChild(_childClass, _attributes, _otherParents); + } + + @Override + public String toString() + { + return "CreateChildTask [object=" + _object + ", childClass=" + _childClass + ", attributes=" + _attributes + + ", otherParents=" + Arrays.toString(_otherParents) + "]"; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/SetAttributeTask.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/SetAttributeTask.java new file mode 100644 index 0000000000..94649434e6 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/SetAttributeTask.java @@ -0,0 +1,74 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration.updater; + +import java.util.concurrent.Callable; + +import org.apache.qpid.server.model.ConfiguredObject; + +public final class SetAttributeTask implements Callable +{ + private ConfiguredObject _object; + private String _attributeName; + private Object _expectedValue; + private Object _desiredValue; + + public SetAttributeTask(ConfiguredObject object, String attributeName, Object expectedValue, Object desiredValue) + { + _object = object; + _attributeName = attributeName; + _expectedValue = expectedValue; + _desiredValue = desiredValue; + } + + public ConfiguredObject getObject() + { + return _object; + } + + public String getAttributeName() + { + return _attributeName; + } + + public Object getExpectedValue() + { + return _expectedValue; + } + + public Object getDesiredValue() + { + return _desiredValue; + } + + @Override + public Object call() + { + return _object.setAttribute(_attributeName, _expectedValue, _desiredValue); + } + + @Override + public String toString() + { + return "SetAttributeTask [object=" + _object + ", attributeName=" + _attributeName + ", expectedValue=" + _expectedValue + + ", desiredValue=" + _desiredValue + "]"; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/TaskExecutor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/TaskExecutor.java new file mode 100644 index 0000000000..671104d413 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/configuration/updater/TaskExecutor.java @@ -0,0 +1,324 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.updater; + +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import javax.security.auth.Subject; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.security.SecurityManager; + +public class TaskExecutor +{ + private static final String TASK_EXECUTION_THREAD_NAME = "Broker-Configuration-Thread"; + private static final Logger LOGGER = Logger.getLogger(TaskExecutor.class); + + private volatile Thread _taskThread; + private final AtomicReference _state; + private volatile ExecutorService _executor; + + public TaskExecutor() + { + _state = new AtomicReference(State.INITIALISING); + } + + public State getState() + { + return _state.get(); + } + + public void start() + { + if (_state.compareAndSet(State.INITIALISING, State.ACTIVE)) + { + LOGGER.debug("Starting task executor"); + _executor = Executors.newFixedThreadPool(1, new ThreadFactory() + { + @Override + public Thread newThread(Runnable r) + { + _taskThread = new Thread(r, TASK_EXECUTION_THREAD_NAME); + return _taskThread; + } + }); + LOGGER.debug("Task executor is started"); + } + } + + public void stopImmediately() + { + if (_state.compareAndSet(State.ACTIVE, State.STOPPED)) + { + ExecutorService executor = _executor; + if (executor != null) + { + LOGGER.debug("Stopping task executor immediately"); + List cancelledTasks = executor.shutdownNow(); + if (cancelledTasks != null) + { + for (Runnable runnable : cancelledTasks) + { + if (runnable instanceof RunnableFuture) + { + ((RunnableFuture) runnable).cancel(true); + } + } + } + _executor = null; + _taskThread = null; + LOGGER.debug("Task executor was stopped immediately. Number of unfinished tasks: " + cancelledTasks.size()); + } + } + } + + public void stop() + { + if (_state.compareAndSet(State.ACTIVE, State.STOPPED)) + { + ExecutorService executor = _executor; + if (executor != null) + { + LOGGER.debug("Stopping task executor"); + executor.shutdown(); + _executor = null; + _taskThread = null; + LOGGER.debug("Task executor is stopped"); + } + } + } + + Future submit(Callable task) + { + checkState(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Submitting task: " + task); + } + Future future = null; + if (isTaskExecutorThread()) + { + Object result = executeTaskAndHandleExceptions(task); + return new ImmediateFuture(result); + } + else + { + future = _executor.submit(new CallableWrapper(task)); + } + return future; + } + + public Object submitAndWait(Callable task) throws CancellationException + { + try + { + Future future = submit(task); + return future.get(); + } + catch (InterruptedException e) + { + throw new RuntimeException("Task execution was interrupted: " + task, e); + } + catch (ExecutionException e) + { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) + { + throw (RuntimeException) cause; + } + else if (cause instanceof Exception) + { + throw new RuntimeException("Failed to execute user task: " + task, cause); + } + else if (cause instanceof Error) + { + throw (Error) cause; + } + else + { + throw new RuntimeException("Failed to execute user task: " + task, cause); + } + } + } + + public boolean isTaskExecutorThread() + { + return Thread.currentThread() == _taskThread; + } + + private void checkState() + { + if (_state.get() != State.ACTIVE) + { + throw new IllegalStateException("Task executor is not in ACTIVE state"); + } + } + + private Object executeTaskAndHandleExceptions(Callable userTask) + { + try + { + return executeTask(userTask); + } + catch (Exception e) + { + if (e instanceof RuntimeException) + { + throw (RuntimeException) e; + } + throw new RuntimeException("Failed to execute user task: " + userTask, e); + } + } + + private Object executeTask(Callable userTask) throws Exception + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Performing task " + userTask); + } + Object result = userTask.call(); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Task " + userTask + " is performed successfully with result:" + result); + } + return result; + } + + private class CallableWrapper implements Callable + { + private Callable _userTask; + private Subject _securityManagerSubject; + private LogActor _actor; + private Subject _contextSubject; + + public CallableWrapper(Callable userWork) + { + _userTask = userWork; + _securityManagerSubject = SecurityManager.getThreadSubject(); + _actor = CurrentActor.get(); + _contextSubject = Subject.getSubject(AccessController.getContext()); + } + + @Override + public Object call() throws Exception + { + SecurityManager.setThreadSubject(_securityManagerSubject); + CurrentActor.set(_actor); + + try + { + Object result = null; + try + { + result = Subject.doAs(_contextSubject, new PrivilegedExceptionAction() + { + @Override + public Object run() throws Exception + { + return executeTask(_userTask); + } + }); + } + catch (PrivilegedActionException e) + { + throw e.getException(); + } + return result; + } + finally + { + try + { + CurrentActor.remove(); + } + catch (Exception e) + { + LOGGER.warn("Unxpected exception on current actor removal", e); + } + try + { + SecurityManager.setThreadSubject(null); + } + catch (Exception e) + { + LOGGER.warn("Unxpected exception on nullifying of subject for a security manager", e); + } + } + } + } + + private class ImmediateFuture implements Future + { + private Object _result; + + public ImmediateFuture(Object result) + { + super(); + this._result = result; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) + { + return false; + } + + @Override + public boolean isCancelled() + { + return false; + } + + @Override + public boolean isDone() + { + return true; + } + + @Override + public Object get() + { + return _result; + } + + @Override + public Object get(long timeout, TimeUnit unit) + { + return get(); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java new file mode 100644 index 0000000000..b933d3f961 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.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.connection; + +import org.apache.log4j.Logger; + +import org.apache.qpid.common.Closeable; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQConnectionModel; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public class ConnectionRegistry implements IConnectionRegistry, Closeable +{ + private List _registry = new CopyOnWriteArrayList(); + + private Logger _logger = Logger.getLogger(ConnectionRegistry.class); + private final Collection _listeners = + new ArrayList(); + + public void initialise() + { + // None required + } + + /** Close all of the currently open connections. */ + public void close() + { + close(IConnectionRegistry.BROKER_SHUTDOWN_REPLY_TEXT); + } + + public void close(final String replyText) + { + synchronized(this) + { + for(AMQConnectionModel conn : _registry) + { + conn.stop(); + } + } + if (_logger.isDebugEnabled()) + { + _logger.debug("Closing connection registry :" + _registry.size() + " connections."); + } + while (!_registry.isEmpty()) + { + AMQConnectionModel connection = _registry.get(0); + closeConnection(connection, AMQConstant.CONNECTION_FORCED, replyText); + } + } + + private void closeConnection(AMQConnectionModel connection, AMQConstant cause, String message) + { + try + { + connection.close(cause, message); + } + catch (Exception e) + { + _logger.warn("Exception closing connection", e); + } + } + + public void registerConnection(AMQConnectionModel connnection) + { + synchronized (this) + { + _registry.add(connnection); + synchronized (_listeners) + { + for(RegistryChangeListener listener : _listeners) + { + listener.connectionRegistered(connnection); + } + } + } + } + + public void deregisterConnection(AMQConnectionModel connnection) + { + synchronized (this) + { + _registry.remove(connnection); + + synchronized (_listeners) + { + for(RegistryChangeListener listener : _listeners) + { + listener.connectionUnregistered(connnection); + } + } + } + } + + public void addRegistryChangeListener(RegistryChangeListener listener) + { + synchronized (_listeners) + { + _listeners.add(listener); + } + } + + public List getConnections() + { + synchronized (this) + { + return new ArrayList(_registry); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java new file mode 100644 index 0000000000..07d934027e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.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.connection; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQConnectionModel; + +import java.util.List; + +public interface IConnectionRegistry +{ + public static final String BROKER_SHUTDOWN_REPLY_TEXT = "Broker is shutting down"; + public static final String VHOST_PASSIVATE_REPLY_TEXT = "Virtual host is being passivated"; + + public void initialise(); + + public void close() throws AMQException; + + public void close(String replyText); + + public List getConnections(); + + public void registerConnection(AMQConnectionModel connnection); + + public void deregisterConnection(AMQConnectionModel connnection); + + void addRegistryChangeListener(RegistryChangeListener listener); + + interface RegistryChangeListener + { + void connectionRegistered(AMQConnectionModel connection); + void connectionUnregistered(AMQConnectionModel connection); + + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java new file mode 100644 index 0000000000..c30ebe17be --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java @@ -0,0 +1,679 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.BindingMessages; +import org.apache.qpid.server.logging.messages.ExchangeMessages; +import org.apache.qpid.server.logging.subjects.BindingLogSubject; +import org.apache.qpid.server.logging.subjects.ExchangeLogSubject; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.store.DurableConfigurationStoreHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public abstract class AbstractExchange implements Exchange +{ + private static final Logger _logger = Logger.getLogger(AbstractExchange.class); + private String _name; + private final AtomicBoolean _closed = new AtomicBoolean(); + + private Exchange _alternateExchange; + + private boolean _durable; + + private VirtualHost _virtualHost; + + private final List _closeTaskList = new CopyOnWriteArrayList(); + + /** + * Whether the exchange is automatically deleted once all queues have detached from it + */ + private boolean _autoDelete; + + //The logSubject for ths exchange + private LogSubject _logSubject; + private Map _referrers = new ConcurrentHashMap(); + + private final CopyOnWriteArrayList _bindings = new CopyOnWriteArrayList(); + private final ExchangeType _type; + private UUID _id; + private final AtomicInteger _bindingCountHigh = new AtomicInteger(); + private final AtomicLong _receivedMessageCount = new AtomicLong(); + private final AtomicLong _receivedMessageSize = new AtomicLong(); + private final AtomicLong _routedMessageCount = new AtomicLong(); + private final AtomicLong _routedMessageSize = new AtomicLong(); + private final AtomicLong _droppedMessageCount = new AtomicLong(); + private final AtomicLong _droppedMessageSize = new AtomicLong(); + + private final CopyOnWriteArrayList _listeners = new CopyOnWriteArrayList(); + + //TODO : persist creation time + private long _createTime = System.currentTimeMillis(); + + public AbstractExchange(final ExchangeType type) + { + _type = type; + } + + @Override + public String getTypeName() + { + return _type.getType(); + } + + public void initialise(UUID id, + VirtualHost host, + String name, + boolean durable, + boolean autoDelete) + throws AMQException + { + _virtualHost = host; + _name = name; + _durable = durable; + _autoDelete = autoDelete; + + _id = id; + _logSubject = new ExchangeLogSubject(this, this.getVirtualHost()); + + // Log Exchange creation + CurrentActor.get().message(ExchangeMessages.CREATED(getType().getType(), name, durable)); + } + + public boolean isDurable() + { + return _durable; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public void close() throws AMQException + { + + if(_closed.compareAndSet(false,true)) + { + if(_alternateExchange != null) + { + _alternateExchange.removeReference(this); + } + + CurrentActor.get().message(_logSubject, ExchangeMessages.DELETED()); + + for(Task task : _closeTaskList) + { + task.onClose(this); + } + _closeTaskList.clear(); + } + } + + public String toString() + { + return getClass().getSimpleName() + "[" + getName() +"]"; + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public final boolean isBound(String bindingKey, Map arguments, AMQQueue queue) + { + for(Binding b : _bindings) + { + if(bindingKey.equals(b.getBindingKey()) && queue == b.getQueue()) + { + return (b.getArguments() == null || b.getArguments().isEmpty()) + ? (arguments == null || arguments.isEmpty()) + : b.getArguments().equals(arguments); + } + } + return false; + } + + public final boolean isBound(String bindingKey, AMQQueue queue) + { + for(Binding b : _bindings) + { + if(bindingKey.equals(b.getBindingKey()) && queue == b.getQueue()) + { + return true; + } + } + return false; + } + + public final boolean isBound(String bindingKey) + { + for(Binding b : _bindings) + { + if(bindingKey.equals(b.getBindingKey())) + { + return true; + } + } + return false; + } + + public final boolean isBound(AMQQueue queue) + { + for(Binding b : _bindings) + { + if(queue == b.getQueue()) + { + return true; + } + } + return false; + } + + @Override + public final boolean isBound(Map arguments, AMQQueue queue) + { + for(Binding b : _bindings) + { + if(queue == b.getQueue() && + ((b.getArguments() == null || b.getArguments().isEmpty()) + ? (arguments == null || arguments.isEmpty()) + : b.getArguments().equals(arguments))) + { + return true; + } + } + return false; + } + + + public final boolean isBound(Map arguments) + { + for(Binding b : _bindings) + { + if(((b.getArguments() == null || b.getArguments().isEmpty()) + ? (arguments == null || arguments.isEmpty()) + : b.getArguments().equals(arguments))) + { + return true; + } + } + return false; + } + + + @Override + public final boolean isBound(String bindingKey, Map arguments) + { + for(Binding b : _bindings) + { + if(b.getBindingKey().equals(bindingKey) && + ((b.getArguments() == null || b.getArguments().isEmpty()) + ? (arguments == null || arguments.isEmpty()) + : b.getArguments().equals(arguments))) + { + return true; + } + } + return false; + } + + public final boolean hasBindings() + { + return !_bindings.isEmpty(); + } + + public Exchange getAlternateExchange() + { + return _alternateExchange; + } + + public void setAlternateExchange(Exchange exchange) + { + if(_alternateExchange != null) + { + _alternateExchange.removeReference(this); + } + if(exchange != null) + { + exchange.addReference(this); + } + _alternateExchange = exchange; + + } + + public void removeReference(ExchangeReferrer exchange) + { + _referrers.remove(exchange); + } + + public void addReference(ExchangeReferrer exchange) + { + _referrers.put(exchange, Boolean.TRUE); + } + + public boolean hasReferrers() + { + return !_referrers.isEmpty(); + } + + public void addCloseTask(final Task task) + { + _closeTaskList.add(task); + } + + public void removeCloseTask(final Task task) + { + _closeTaskList.remove(task); + } + + public final void doAddBinding(final Binding binding) + { + _bindings.add(binding); + int bindingCountSize = _bindings.size(); + int maxBindingsSize; + while((maxBindingsSize = _bindingCountHigh.get()) < bindingCountSize) + { + _bindingCountHigh.compareAndSet(maxBindingsSize, bindingCountSize); + } + for(BindingListener listener : _listeners) + { + listener.bindingAdded(this, binding); + } + onBind(binding); + } + + public long getBindingCountHigh() + { + return _bindingCountHigh.get(); + } + + public final void doRemoveBinding(final Binding binding) + { + onUnbind(binding); + for(BindingListener listener : _listeners) + { + listener.bindingRemoved(this, binding); + } + _bindings.remove(binding); + } + + public final Collection getBindings() + { + return Collections.unmodifiableList(_bindings); + } + + protected abstract void onBind(final Binding binding); + + protected abstract void onUnbind(final Binding binding); + + + public String getName() + { + return _name.toString(); + } + + public ExchangeType getType() + { + return _type; + } + + public Map getArguments() + { + return Collections.emptyMap(); + } + + public UUID getId() + { + return _id; + } + + public long getBindingCount() + { + return getBindings().size(); + } + + public final List route(final InboundMessage message) + { + _receivedMessageCount.incrementAndGet(); + _receivedMessageSize.addAndGet(message.getSize()); + List queues = doRoute(message); + List allQueues = queues; + + boolean deletedQueues = false; + + for(BaseQueue q : allQueues) + { + if(q.isDeleted()) + { + if(!deletedQueues) + { + deletedQueues = true; + queues = new ArrayList(allQueues); + } + if(_logger.isDebugEnabled()) + { + _logger.debug("Exchange: " + getName() + " - attempt to enqueue message onto deleted queue " + q.getName()); + } + queues.remove(q); + } + } + + + if(!queues.isEmpty()) + { + _routedMessageCount.incrementAndGet(); + _routedMessageSize.addAndGet(message.getSize()); + } + else + { + _droppedMessageCount.incrementAndGet(); + _droppedMessageSize.addAndGet(message.getSize()); + } + return queues; + } + + protected abstract List doRoute(final InboundMessage message); + + public long getMsgReceives() + { + return _receivedMessageCount.get(); + } + + public long getMsgRoutes() + { + return _routedMessageCount.get(); + } + + public long getMsgDrops() + { + return _droppedMessageCount.get(); + } + + public long getByteReceives() + { + return _receivedMessageSize.get(); + } + + public long getByteRoutes() + { + return _routedMessageSize.get(); + } + + public long getByteDrops() + { + return _droppedMessageSize.get(); + } + + public long getCreateTime() + { + return _createTime; + } + + public void addBindingListener(final BindingListener listener) + { + _listeners.add(listener); + } + + public void removeBindingListener(final BindingListener listener) + { + _listeners.remove(listener); + } + + @Override + public boolean addBinding(String bindingKey, AMQQueue queue, Map arguments) + throws AMQSecurityException, AMQInternalException + { + return makeBinding(null, bindingKey, queue, arguments, false, false); + } + + @Override + public boolean replaceBinding(final UUID id, final String bindingKey, + final AMQQueue queue, + final Map arguments) + throws AMQSecurityException, AMQInternalException + { + return makeBinding(id, bindingKey, queue, arguments, false, true); + } + + @Override + public void restoreBinding(final UUID id, final String bindingKey, final AMQQueue queue, + final Map argumentMap) + throws AMQSecurityException, AMQInternalException + { + makeBinding(id, bindingKey,queue, argumentMap,true, false); + } + + @Override + public void removeBinding(final Binding b) throws AMQSecurityException, AMQInternalException + { + removeBinding(b.getBindingKey(), b.getQueue(), b.getArguments()); + } + + @Override + public Binding removeBinding(String bindingKey, AMQQueue queue, Map arguments) + throws AMQSecurityException, AMQInternalException + { + assert queue != null; + + if (bindingKey == null) + { + bindingKey = ""; + } + if (arguments == null) + { + arguments = Collections.emptyMap(); + } + + // The default exchange bindings must reflect the existence of queues, allow + // all operations on it to succeed. It is up to the broker to prevent illegal + // attempts at binding to this exchange, not the ACLs. + // Check access + if (!_virtualHost.getSecurityManager().authoriseUnbind(this, bindingKey, queue)) + { + throw new AMQSecurityException("Permission denied: unbinding " + bindingKey); + } + + BindingImpl b = _bindingsMap.remove(new BindingImpl(null, bindingKey,queue,arguments)); + + if (b != null) + { + doRemoveBinding(b); + queue.removeBinding(b); + removeCloseTask(b); + queue.removeQueueDeleteTask(b); + + if (b.isDurable()) + { + DurableConfigurationStoreHelper.removeBinding(_virtualHost.getDurableConfigurationStore(), b); + } + b.logDestruction(); + } + + return b; + } + + + @Override + public Binding getBinding(String bindingKey, AMQQueue queue, Map arguments) + { + assert queue != null; + + if(bindingKey == null) + { + bindingKey = ""; + } + + if(arguments == null) + { + arguments = Collections.emptyMap(); + } + + BindingImpl b = new BindingImpl(null, bindingKey,queue,arguments); + return _bindingsMap.get(b); + } + + private final ConcurrentHashMap _bindingsMap = new ConcurrentHashMap(); + + private boolean makeBinding(UUID id, + String bindingKey, + AMQQueue queue, + Map arguments, + boolean restore, + boolean force) throws AMQSecurityException, AMQInternalException + { + assert queue != null; + + if (bindingKey == null) + { + bindingKey = ""; + } + if (arguments == null) + { + arguments = Collections.emptyMap(); + } + + //Perform ACLs + if (!_virtualHost.getSecurityManager().authoriseBind(AbstractExchange.this, queue, bindingKey)) + { + throw new AMQSecurityException("Permission denied: binding " + bindingKey); + } + + if (id == null) + { + id = UUIDGenerator.generateBindingUUID(getName(), + queue.getName(), + bindingKey, + _virtualHost.getName()); + } + BindingImpl b = new BindingImpl(id, bindingKey, queue, arguments); + BindingImpl existingMapping = _bindingsMap.putIfAbsent(b, b); + if (existingMapping == null || force) + { + if (existingMapping != null) + { + removeBinding(existingMapping); + } + + if (b.isDurable() && !restore) + { + DurableConfigurationStoreHelper.createBinding(_virtualHost.getDurableConfigurationStore(), b); + } + + queue.addQueueDeleteTask(b); + addCloseTask(b); + queue.addBinding(b); + doAddBinding(b); + b.logCreation(); + + return true; + } + else + { + return false; + } + } + + private final class BindingImpl extends Binding implements AMQQueue.Task, Task + { + private final BindingLogSubject _logSubject; + //TODO : persist creation time + private long _createTime = System.currentTimeMillis(); + + private BindingImpl(UUID id, + String bindingKey, + final AMQQueue queue, + final Map arguments) + { + super(id, bindingKey, queue, AbstractExchange.this, arguments); + _logSubject = new BindingLogSubject(bindingKey,AbstractExchange.this,queue); + + } + + + public void doTask(final AMQQueue queue) throws AMQException + { + removeBinding(this); + } + + public void onClose(final Exchange exchange) throws AMQSecurityException, AMQInternalException + { + removeBinding(this); + } + + void logCreation() + { + CurrentActor.get().message(_logSubject, BindingMessages.CREATED(String.valueOf(getArguments()), + getArguments() != null + && !getArguments().isEmpty())); + } + + void logDestruction() + { + CurrentActor.get().message(_logSubject, BindingMessages.DELETED()); + } + + public String getOrigin() + { + return (String) getArguments().get("qpid.fed.origin"); + } + + public long getCreateTime() + { + return _createTime; + } + + public boolean isDurable() + { + return getQueue().isDurable() && getExchange().isDurable(); + } + + } + + public static interface Task + { + public void onClose(Exchange exchange) throws AMQSecurityException, AMQInternalException; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchange.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchange.java new file mode 100644 index 0000000000..aed2ddb8cf --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchange.java @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ExchangeMessages; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class DefaultExchange implements Exchange +{ + + private final QueueRegistry _queueRegistry; + private UUID _id; + private VirtualHost _virtualHost; + private static final Logger _logger = Logger.getLogger(DefaultExchange.class); + private final AtomicBoolean _closed = new AtomicBoolean(); + + private LogSubject _logSubject; + private Map _referrers = new ConcurrentHashMap(); + + public DefaultExchange(QueueRegistry queueRegistry) + { + _queueRegistry = queueRegistry; + } + + + @Override + public void initialise(UUID id, + VirtualHost host, + String name, + boolean durable, + boolean autoDelete) throws AMQException + { + _id = id; + _virtualHost = host; + } + + @Override + public String getName() + { + return ExchangeDefaults.DEFAULT_EXCHANGE_NAME; + } + + @Override + public ExchangeType getType() + { + return DirectExchange.TYPE; + } + + @Override + public long getBindingCount() + { + return _virtualHost.getQueues().size(); + } + + @Override + public long getByteDrops() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public long getByteReceives() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public long getMsgDrops() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public long getMsgReceives() + { + return 0; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public boolean addBinding(String bindingKey, AMQQueue queue, Map arguments) + throws AMQSecurityException, AMQInternalException + { + throw new AMQSecurityException("Cannot add bindings to the default exchange"); + } + + @Override + public boolean replaceBinding(UUID id, String bindingKey, AMQQueue queue, Map arguments) + throws AMQSecurityException, AMQInternalException + { + throw new AMQSecurityException("Cannot replace bindings on the default exchange"); + } + + @Override + public void restoreBinding(UUID id, String bindingKey, AMQQueue queue, Map argumentMap) + throws AMQSecurityException, AMQInternalException + { + _logger.warn("Bindings to the default exchange should not be stored in the configuration store"); + } + + @Override + public void removeBinding(Binding b) throws AMQSecurityException, AMQInternalException + { + throw new AMQSecurityException("Cannot remove bindings to the default exchange"); + } + + @Override + public Binding removeBinding(String bindingKey, AMQQueue queue, Map arguments) + throws AMQSecurityException, AMQInternalException + { + throw new AMQSecurityException("Cannot remove bindings to the default exchange"); + } + + @Override + public Binding getBinding(String bindingKey, AMQQueue queue, Map arguments) + { + if(_virtualHost.getQueue(bindingKey) == queue && (arguments == null || arguments.isEmpty())) + { + return convertToBinding(queue); + } + else + { + return null; + } + + } + + private Binding convertToBinding(AMQQueue queue) + { + String queueName = queue.getName(); + + UUID exchangeId = UUIDGenerator.generateBindingUUID(ExchangeDefaults.DEFAULT_EXCHANGE_NAME, + queueName, + queueName, + _virtualHost.getName()); + + return new Binding(exchangeId, queueName, queue, this, Collections.EMPTY_MAP); + } + + @Override + public String getTypeName() + { + return getType().getType(); + } + + @Override + public boolean isDurable() + { + return false; + } + + @Override + public boolean isAutoDelete() + { + return false; + } + + @Override + public void close() throws AMQException + { + if(_closed.compareAndSet(false,true)) + { + + CurrentActor.get().message(_logSubject, ExchangeMessages.DELETED()); + + } + } + + @Override + public List route(InboundMessage message) + { + AMQQueue q = _virtualHost.getQueue(message.getRoutingKey()); + if(q == null) + { + List noQueues = Collections.emptyList(); + return noQueues; + } + else + { + return Collections.singletonList(q); + } + + } + + @Override + public boolean isBound(AMQQueue queue) + { + return _virtualHost.getQueue(queue.getName()) == queue; + } + + @Override + public boolean hasBindings() + { + return getBindingCount() != 0; + } + + @Override + public boolean isBound(String bindingKey, AMQQueue queue) + { + return isBound(queue) && queue.getName().equals(bindingKey); + } + + @Override + public boolean isBound(String bindingKey, Map arguments, AMQQueue queue) + { + return isBound(bindingKey, queue) && (arguments == null || arguments.isEmpty()); + } + + @Override + public boolean isBound(Map arguments, AMQQueue queue) + { + return (arguments == null || arguments.isEmpty()) && isBound(queue); + } + + @Override + public boolean isBound(String bindingKey, Map arguments) + { + return (arguments == null || arguments.isEmpty()) && isBound(bindingKey); + } + + @Override + public boolean isBound(Map arguments) + { + return (arguments == null || arguments.isEmpty()) && hasBindings(); + } + + @Override + public boolean isBound(String bindingKey) + { + return _virtualHost.getQueue(bindingKey) != null; + } + + @Override + public Exchange getAlternateExchange() + { + return null; + } + + @Override + public void setAlternateExchange(Exchange exchange) + { + _logger.warn("Cannot set the alternate exchange for the default exchange"); + } + + @Override + public void removeReference(ExchangeReferrer exchange) + { + _referrers.remove(exchange); + } + + @Override + public void addReference(ExchangeReferrer exchange) + { + _referrers.put(exchange, Boolean.TRUE); + } + + @Override + public boolean hasReferrers() + { + return !_referrers.isEmpty(); + } + + @Override + public Collection getBindings() + { + List bindings = new ArrayList(); + for(AMQQueue q : _virtualHost.getQueues()) + { + bindings.add(convertToBinding(q)); + } + return bindings; + } + + @Override + public void addBindingListener(BindingListener listener) + { + _queueRegistry.addRegistryChangeListener(convertListener(listener)); + } + + private QueueRegistry.RegistryChangeListener convertListener(final BindingListener listener) + { + return new QueueRegistry.RegistryChangeListener() + { + @Override + public void queueRegistered(AMQQueue queue) + { + listener.bindingAdded(DefaultExchange.this, convertToBinding(queue)); + } + + @Override + public void queueUnregistered(AMQQueue queue) + { + listener.bindingRemoved(DefaultExchange.this, convertToBinding(queue)); + } + }; + } + + @Override + public void removeBindingListener(BindingListener listener) + { + // TODO + } + + @Override + public UUID getId() + { + return _id; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java new file mode 100644 index 0000000000..612fa855a4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java @@ -0,0 +1,145 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.AMQUnknownExchangeType; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class DefaultExchangeFactory implements ExchangeFactory +{ + public static final String DEFAULT_DLE_NAME_SUFFIX = "_DLE"; + + private static final Logger LOGGER = Logger.getLogger(DefaultExchangeFactory.class); + + private static final String[] BASE_EXCHANGE_TYPES = + new String[]{ExchangeDefaults.DIRECT_EXCHANGE_CLASS, + ExchangeDefaults.FANOUT_EXCHANGE_CLASS, + ExchangeDefaults.HEADERS_EXCHANGE_CLASS, + ExchangeDefaults.TOPIC_EXCHANGE_CLASS}; + + private final VirtualHost _host; + private Map> _exchangeClassMap = new HashMap>(); + + public DefaultExchangeFactory(VirtualHost host) + { + _host = host; + + @SuppressWarnings("rawtypes") + Iterable exchangeTypes = loadExchangeTypes(); + for (ExchangeType exchangeType : exchangeTypes) + { + String typeName = exchangeType.getType(); + + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("Registering exchange type '" + typeName + "' using class '" + exchangeType.getClass().getName() + "'"); + } + + if(_exchangeClassMap.containsKey(typeName)) + { + ExchangeType existingType = _exchangeClassMap.get(typeName); + + throw new IllegalStateException("ExchangeType with type name '" + typeName + "' is already registered using class '" + + existingType.getClass().getName() + "', can not register class '" + + exchangeType.getClass().getName() + "'"); + } + + _exchangeClassMap.put(typeName, exchangeType); + } + + for(String type : BASE_EXCHANGE_TYPES) + { + if(!_exchangeClassMap.containsKey(type)) + { + throw new IllegalStateException("Did not find expected exchange type: " + type); + } + } + } + + @SuppressWarnings("rawtypes") + protected Iterable loadExchangeTypes() + { + return new QpidServiceLoader().atLeastOneInstanceOf(ExchangeType.class); + } + + public Collection> getRegisteredTypes() + { + return _exchangeClassMap.values(); + } + + public Collection> getPublicCreatableTypes() + { + Collection> publicTypes = + new ArrayList>(); + publicTypes.addAll(_exchangeClassMap.values()); + + return publicTypes; + } + + public Exchange createExchange(String exchange, String type, boolean durable, boolean autoDelete) + throws AMQException + { + + UUID id = UUIDGenerator.generateExchangeUUID(exchange, _host.getName()); + return createExchange(id, exchange, type, durable, autoDelete); + } + + public Exchange createExchange(UUID id, String exchange, String type, boolean durable, boolean autoDelete) + throws AMQException + { + // Check access + if (!_host.getSecurityManager().authoriseCreateExchange(autoDelete, durable, exchange, null, null, null, type)) + { + String description = "Permission denied: exchange-name '" + exchange + "'"; + throw new AMQSecurityException(description); + } + + ExchangeType exchType = _exchangeClassMap.get(type); + if (exchType == null) + { + throw new AMQUnknownExchangeType("Unknown exchange type: " + type,null); + } + + Exchange e = exchType.newInstance(id, _host, exchange, durable, autoDelete); + return e; + } + + @Override + public Exchange restoreExchange(UUID id, String exchange, String type, boolean autoDelete) throws AMQException + { + return createExchange(id, exchange, type, true, autoDelete); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java new file mode 100644 index 0000000000..b54f995b5e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.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 org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class DefaultExchangeRegistry implements ExchangeRegistry +{ + private static final Logger LOGGER = Logger.getLogger(DefaultExchangeRegistry.class); + /** + * Maps from exchange name to exchange instance + */ + private ConcurrentMap _exchangeMap = new ConcurrentHashMap(); + + private Exchange _defaultExchange; + + private final VirtualHost _host; + private final QueueRegistry _queueRegistry; + + private final Collection _listeners = + Collections.synchronizedCollection(new ArrayList()); + + public DefaultExchangeRegistry(VirtualHost host, QueueRegistry queueRegistry) + { + _host = host; + _queueRegistry = queueRegistry; + } + + public void initialise(ExchangeFactory exchangeFactory) throws AMQException + { + //create 'standard' exchanges: + new ExchangeInitialiser().initialise(exchangeFactory, this, getDurableConfigurationStore()); + + _defaultExchange = new DefaultExchange(_queueRegistry); + + UUID defaultExchangeId = + UUIDGenerator.generateExchangeUUID(ExchangeDefaults.DEFAULT_EXCHANGE_NAME, _host.getName()); + + _defaultExchange.initialise(defaultExchangeId, _host, ExchangeDefaults.DEFAULT_EXCHANGE_NAME,false, false); + + } + + public DurableConfigurationStore getDurableConfigurationStore() + { + return _host.getDurableConfigurationStore(); + } + + public void registerExchange(Exchange exchange) throws AMQException + { + _exchangeMap.put(exchange.getName(), exchange); + synchronized (_listeners) + { + for(RegistryChangeListener listener : _listeners) + { + listener.exchangeRegistered(exchange); + } + + } + } + + public void setDefaultExchange(Exchange exchange) + { + _defaultExchange = exchange; + } + + public Exchange getDefaultExchange() + { + return _defaultExchange; + } + + public void unregisterExchange(String name, boolean inUse) throws AMQException + { + final Exchange exchange = _exchangeMap.get(name); + if (exchange == null) + { + throw new AMQException(AMQConstant.NOT_FOUND, "Unknown exchange " + name, null); + } + + if (!_host.getSecurityManager().authoriseDelete(exchange)) + { + throw new AMQSecurityException(); + } + + // TODO: check inUse argument + + Exchange e = _exchangeMap.remove(name); + if (e != null) + { + e.close(); + + synchronized (_listeners) + { + for(RegistryChangeListener listener : _listeners) + { + listener.exchangeUnregistered(exchange); + } + } + + } + else + { + throw new AMQException("Unknown exchange " + name); + } + } + + public Collection getExchanges() + { + return new ArrayList(_exchangeMap.values()); + } + + public void addRegistryChangeListener(RegistryChangeListener listener) + { + _listeners.add(listener); + } + + public Exchange getExchange(String name) + { + if ((name == null) || name.length() == 0) + { + return getDefaultExchange(); + } + else + { + return _exchangeMap.get(name); + } + } + + @Override + public void clearAndUnregisterMbeans() + { + for (final Exchange exchange : getExchanges()) + { + //TODO: this is a bit of a hack, what if the listeners aren't aware + //that we are just unregistering the MBean because of HA, and aren't + //actually removing the exchange as such. + synchronized (_listeners) + { + for(RegistryChangeListener listener : _listeners) + { + listener.exchangeUnregistered(exchange); + } + } + } + _exchangeMap.clear(); + } + + @Override + public synchronized Exchange getExchange(UUID exchangeId) + { + if (exchangeId == null) + { + return getDefaultExchange(); + } + else + { + Collection exchanges = _exchangeMap.values(); + for (Exchange exchange : exchanges) + { + if (exchange.getId().equals(exchangeId)) + { + return exchange; + } + } + return null; + } + } + + public boolean isReservedExchangeName(String name) + { + if (name == null || ExchangeDefaults.DEFAULT_EXCHANGE_NAME.equals(name) + || name.startsWith("amq.") || name.startsWith("qpid.")) + { + return true; + } + Collection> registeredTypes = _host.getExchangeTypes(); + for (ExchangeType type : registeredTypes) + { + if (type.getDefaultExchangeName().equals(name)) + { + return true; + } + } + return false; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java new file mode 100644 index 0000000000..1e022c994e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java @@ -0,0 +1,215 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.AMQInvalidArgumentException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.filter.JMSSelectorFilter; +import org.apache.qpid.server.filter.MessageFilter; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +public class DirectExchange extends AbstractExchange +{ + + private static final Logger _logger = Logger.getLogger(DirectExchange.class); + + private static final class BindingSet + { + private CopyOnWriteArraySet _bindings = new CopyOnWriteArraySet(); + private List _unfilteredQueues = new ArrayList(); + private Map _filteredQueues = new HashMap(); + + public synchronized void addBinding(Binding binding) + { + _bindings.add(binding); + recalculateQueues(); + } + + public synchronized void removeBinding(Binding binding) + { + _bindings.remove(binding); + recalculateQueues(); + } + + private void recalculateQueues() + { + List queues = new ArrayList(_bindings.size()); + Map filteredQueues = new HashMap(); + + for(Binding b : _bindings) + { + + if(FilterSupport.argumentsContainFilter(b.getArguments())) + { + try + { + MessageFilter filter = FilterSupport.createMessageFilter(b.getArguments(), b.getQueue()); + filteredQueues.put(b.getQueue(),filter); + } + catch (AMQInvalidArgumentException e) + { + _logger.warn("Binding ignored: cannot parse filter on binding of queue '"+b.getQueue().getName() + + "' to exchange '" + b.getExchange().getName() + + "' with arguments: " + b.getArguments(), e); + } + + } + else + { + + if(!queues.contains(b.getQueue())) + { + queues.add(b.getQueue()); + } + } + } + _unfilteredQueues = queues; + _filteredQueues = filteredQueues; + } + + + public List getUnfilteredQueues() + { + return _unfilteredQueues; + } + + public CopyOnWriteArraySet getBindings() + { + return _bindings; + } + + public boolean hasFilteredQueues() + { + return !_filteredQueues.isEmpty(); + } + + public Map getFilteredQueues() + { + return _filteredQueues; + } + } + + private final ConcurrentHashMap _bindingsByKey = + new ConcurrentHashMap(); + + public static final ExchangeType TYPE = new DirectExchangeType(); + + public DirectExchange() + { + super(TYPE); + } + + public List doRoute(InboundMessage payload) + { + + final String routingKey = payload.getRoutingKey(); + + BindingSet bindings = _bindingsByKey.get(routingKey == null ? "" : routingKey); + + if(bindings != null) + { + List queues = bindings.getUnfilteredQueues(); + + if(bindings.hasFilteredQueues()) + { + Set queuesSet = new HashSet(queues); + + Map filteredQueues = bindings.getFilteredQueues(); + for(Map.Entry entry : filteredQueues.entrySet()) + { + if(!queuesSet.contains(entry.getKey())) + { + MessageFilter filter = entry.getValue(); + if(filter.matches(payload)) + { + queuesSet.add(entry.getKey()); + } + } + } + if(queues.size() != queuesSet.size()) + { + queues = new ArrayList(queuesSet); + } + } + return queues; + } + else + { + return Collections.emptyList(); + } + + + } + + protected void onBind(final Binding binding) + { + String bindingKey = binding.getBindingKey(); + AMQQueue queue = binding.getQueue(); + + assert queue != null; + assert bindingKey != null; + + BindingSet bindings = _bindingsByKey.get(bindingKey); + + if(bindings == null) + { + bindings = new BindingSet(); + BindingSet newBindings; + if((newBindings = _bindingsByKey.putIfAbsent(bindingKey, bindings)) != null) + { + bindings = newBindings; + } + } + + bindings.addBinding(binding); + + } + + protected void onUnbind(final Binding binding) + { + assert binding != null; + + BindingSet bindings = _bindingsByKey.get(binding.getBindingKey()); + if(bindings != null) + { + bindings.removeBinding(binding); + } + + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DirectExchangeType.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DirectExchangeType.java new file mode 100644 index 0000000000..d61d10155e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/DirectExchangeType.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.exchange; + +import java.util.UUID; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class DirectExchangeType implements ExchangeType +{ + @Override + public String getType() + { + return ExchangeDefaults.DIRECT_EXCHANGE_CLASS; + } + + public DirectExchange newInstance(UUID id, VirtualHost host, + String name, + boolean durable, + boolean autoDelete) throws AMQException + { + DirectExchange exch = new DirectExchange(); + exch.initialise(id, host,name,durable, autoDelete); + return exch; + } + + public String getDefaultExchangeName() + { + return ExchangeDefaults.DIRECT_EXCHANGE_NAME; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/Exchange.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/Exchange.java new file mode 100644 index 0000000000..d05e731daa --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/Exchange.java @@ -0,0 +1,168 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public interface Exchange extends ExchangeReferrer +{ + void initialise(UUID id, VirtualHost host, String name, boolean durable, boolean autoDelete) + throws AMQException; + + + UUID getId(); + + String getName(); + + ExchangeType getType(); + + String getTypeName(); + + boolean isDurable(); + + /** + * @return true if the exchange will be deleted after all queues have been detached + */ + boolean isAutoDelete(); + + Exchange getAlternateExchange(); + + void setAlternateExchange(Exchange exchange); + + long getBindingCount(); + + long getByteDrops(); + + long getByteReceives(); + + long getMsgDrops(); + + long getMsgReceives(); + + + boolean addBinding(String bindingKey, AMQQueue queue, Map arguments) + throws AMQSecurityException, AMQInternalException; + + boolean replaceBinding(UUID id, String bindingKey, + AMQQueue queue, + Map arguments) + throws AMQSecurityException, AMQInternalException; + + void restoreBinding(UUID id, String bindingKey, AMQQueue queue, + Map argumentMap) + throws AMQSecurityException, AMQInternalException; + + void removeBinding(Binding b) throws AMQSecurityException, AMQInternalException; + + Binding removeBinding(String bindingKey, AMQQueue queue, Map arguments) + throws AMQSecurityException, AMQInternalException; + + Binding getBinding(String bindingKey, AMQQueue queue, Map arguments); + + void close() throws AMQException; + + /** + * Returns a list of queues to which to route this message. If there are + * no queues the empty list must be returned. + * + * @return list of queues to which to route the message. + */ + List route(InboundMessage message); + + + /** + * Determines whether a message would be isBound to a particular queue using a specific routing key and arguments + * @param bindingKey + * @param arguments + * @param queue + * @return + * @throws AMQException + */ + + boolean isBound(String bindingKey, Map arguments, AMQQueue queue); + + /** + * Determines whether a message would be isBound to a particular queue using a specific routing key + * @param bindingKey + * @param queue + * @return + * @throws AMQException + */ + + boolean isBound(String bindingKey, AMQQueue queue); + + /** + * Determines whether a message is routing to any queue using a specific _routing key + * @param bindingKey + * @return + * @throws AMQException + */ + boolean isBound(String bindingKey); + + /** + * Returns true if this exchange has at least one binding associated with it. + * @return + * @throws AMQException + */ + boolean hasBindings(); + + Collection getBindings(); + + boolean isBound(AMQQueue queue); + + boolean isBound(Map arguments); + + boolean isBound(String bindingKey, Map arguments); + + boolean isBound(Map arguments, AMQQueue queue); + + void removeReference(ExchangeReferrer exchange); + + void addReference(ExchangeReferrer exchange); + + boolean hasReferrers(); + + + + public interface BindingListener + { + void bindingAdded(Exchange exchange, Binding binding); + void bindingRemoved(Exchange exchange, Binding binding); + } + + public void addBindingListener(BindingListener listener); + + public void removeBindingListener(BindingListener listener); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java new file mode 100644 index 0000000000..f364691666 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.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.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.plugin.ExchangeType; + +import java.util.Collection; +import java.util.UUID; + + +public interface ExchangeFactory +{ + + Collection> getRegisteredTypes(); + + Collection> getPublicCreatableTypes(); + + Exchange createExchange(String exchange, String type, boolean durable, boolean autoDelete) throws AMQException; + + Exchange createExchange(UUID id, String exchange, String type, boolean durable, boolean autoDelete) throws AMQException; + Exchange restoreExchange(UUID id, String exchange, String type, boolean autoDelete) throws AMQException; + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java new file mode 100644 index 0000000000..c77f114428 --- /dev/null +++ b/qpid/java/broker-core/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. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Represents failure to unregister exchange that is in use. + *
+ * + * @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-core/src/main/java/org/apache/qpid/server/exchange/ExchangeInitialiser.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeInitialiser.java new file mode 100644 index 0000000000..6dbc1d54d1 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeInitialiser.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.exchange; + + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.store.DurableConfigurationStoreHelper; +import org.apache.qpid.server.store.DurableConfigurationStore; + +public class ExchangeInitialiser +{ + public void initialise(ExchangeFactory factory, ExchangeRegistry registry, DurableConfigurationStore store) throws AMQException + { + for (ExchangeType type : factory.getRegisteredTypes()) + { + define (registry, factory, type.getDefaultExchangeName(), type.getType(), store); + } + + } + + private void define(ExchangeRegistry r, ExchangeFactory f, + String name, String type, DurableConfigurationStore store) throws AMQException + { + if(r.getExchange(name)== null) + { + Exchange exchange = f.createExchange(name, type, true, false); + r.registerExchange(exchange); + if(exchange.isDurable()) + { + DurableConfigurationStoreHelper.createExchange(store, exchange); + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeReferrer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeReferrer.java new file mode 100755 index 0000000000..e41d63d97d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeReferrer.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.exchange; + +public interface ExchangeReferrer +{ +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java new file mode 100644 index 0000000000..53ee7de661 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java @@ -0,0 +1,69 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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; +import java.util.UUID; + + +public interface ExchangeRegistry +{ + void registerExchange(Exchange exchange) throws AMQException; + + Exchange getDefaultExchange(); + + void initialise(ExchangeFactory exchangeFactory) throws AMQException; + + Exchange getExchange(String exchangeName); + + /** + * Unregister an exchange + * @param exchange name of the exchange to delete + * @param ifUnused if true, do NOT delete the exchange if it is in use (has queues bound to it) + * @throws ExchangeInUseException when the exchange cannot be deleted because it is in use + * @throws AMQException + */ + void unregisterExchange(String exchange, boolean ifUnused) throws ExchangeInUseException, AMQException; + + void clearAndUnregisterMbeans(); + + Exchange getExchange(UUID exchangeId); + + Collection getExchanges(); + + void addRegistryChangeListener(RegistryChangeListener listener); + + /** + * Validates the name of user custom exchange. + *

+ * Return true if the exchange name is reserved and false otherwise. + */ + boolean isReservedExchangeName(String name); + + interface RegistryChangeListener + { + void exchangeRegistered(Exchange exchange); + void exchangeUnregistered(Exchange exchange); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java new file mode 100644 index 0000000000..cd830d69a9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java @@ -0,0 +1,224 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.exchange; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQInvalidArgumentException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.filter.MessageFilter; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; + +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +public class FanoutExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(FanoutExchange.class); + + private static final Integer ONE = Integer.valueOf(1); + + /** + * Maps from queue name to queue instances + */ + private final Map _queues = new HashMap(); + private final CopyOnWriteArrayList _unfilteredQueues = new CopyOnWriteArrayList(); + private final CopyOnWriteArrayList _filteredQueues = new CopyOnWriteArrayList(); + + private final AtomicReference>> _filteredBindings = + new AtomicReference>>(); + { + Map> emptyMap = Collections.emptyMap(); + _filteredBindings.set(emptyMap); + } + + + + public static final ExchangeType TYPE = new FanoutExchangeType(); + + public FanoutExchange() + { + super(TYPE); + } + + public ArrayList doRoute(InboundMessage payload) + { + + for(Binding b : getBindings()) + { + b.incrementMatches(); + } + + final ArrayList result = new ArrayList(_unfilteredQueues); + + + final Map> filteredBindings = _filteredBindings.get(); + if(!_filteredQueues.isEmpty()) + { + for(AMQQueue q : _filteredQueues) + { + final Map bindingMessageFilterMap = filteredBindings.get(q); + if(!(bindingMessageFilterMap == null || result.contains(q))) + { + for(MessageFilter filter : bindingMessageFilterMap.values()) + { + if(filter.matches(payload)) + { + result.add(q); + break; + } + } + } + } + + } + + + if (_logger.isDebugEnabled()) + { + _logger.debug("Publishing message to queue " + result); + } + + return result; + + } + + + protected synchronized void onBind(final Binding binding) + { + AMQQueue queue = binding.getQueue(); + assert queue != null; + if(binding.getArguments() == null || binding.getArguments().isEmpty() || !FilterSupport.argumentsContainFilter(binding.getArguments())) + { + + Integer oldVal; + if(_queues.containsKey(queue)) + { + _queues.put(queue,_queues.get(queue)+1); + } + else + { + _queues.put(queue, ONE); + _unfilteredQueues.add(queue); + // No longer any reason to check filters for this queue + _filteredQueues.remove(queue); + } + + } + else + { + try + { + + HashMap> filteredBindings = + new HashMap>(_filteredBindings.get()); + + Map bindingsForQueue = filteredBindings.remove(binding.getQueue()); + final + MessageFilter messageFilter = + FilterSupport.createMessageFilter(binding.getArguments(), binding.getQueue()); + + if(bindingsForQueue != null) + { + bindingsForQueue = new HashMap(bindingsForQueue); + bindingsForQueue.put(binding, messageFilter); + } + else + { + bindingsForQueue = Collections.singletonMap(binding, messageFilter); + if(!_unfilteredQueues.contains(queue)) + { + _filteredQueues.add(queue); + } + } + + filteredBindings.put(binding.getQueue(), bindingsForQueue); + + _filteredBindings.set(filteredBindings); + + } + catch (AMQInvalidArgumentException e) + { + _logger.warn("Cannoy bind queue " + queue + " to exchange this " + this + " beacuse selector cannot be parsed.", e); + return; + } + } + if (_logger.isDebugEnabled()) + { + _logger.debug("Binding queue " + queue + + " with routing key " + binding.getBindingKey() + " to exchange " + this); + } + } + + protected synchronized void onUnbind(final Binding binding) + { + AMQQueue queue = binding.getQueue(); + if(binding.getArguments() == null || binding.getArguments().isEmpty() || !FilterSupport.argumentsContainFilter(binding.getArguments())) + { + Integer oldValue = _queues.remove(queue); + if(ONE.equals(oldValue)) + { + // should start checking filters for this queue + if(_filteredBindings.get().containsKey(queue)) + { + _filteredQueues.add(queue); + } + _unfilteredQueues.remove(queue); + } + else + { + _queues.put(queue,oldValue-1); + } + } + else // we are removing a binding with filters + { + HashMap> filteredBindings = + new HashMap>(_filteredBindings.get()); + + Map bindingsForQueue = filteredBindings.remove(binding.getQueue()); + if(bindingsForQueue.size()>1) + { + bindingsForQueue = new HashMap(bindingsForQueue); + bindingsForQueue.remove(binding); + filteredBindings.put(binding.getQueue(),bindingsForQueue); + } + else + { + _filteredQueues.remove(queue); + } + _filteredBindings.set(filteredBindings); + + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FanoutExchangeType.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FanoutExchangeType.java new file mode 100644 index 0000000000..ac864df02c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FanoutExchangeType.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.exchange; + +import java.util.UUID; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class FanoutExchangeType implements ExchangeType +{ + @Override + public String getType() + { + return ExchangeDefaults.FANOUT_EXCHANGE_CLASS; + } + + public FanoutExchange newInstance(UUID id, VirtualHost host, String name, + boolean durable, boolean autoDelete) + throws AMQException + { + FanoutExchange exch = new FanoutExchange(); + exch.initialise(id, host, name, durable, autoDelete); + return exch; + } + + public String getDefaultExchangeName() + { + return ExchangeDefaults.FANOUT_EXCHANGE_NAME; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FilterSupport.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FilterSupport.java new file mode 100644 index 0000000000..e78516cf69 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/FilterSupport.java @@ -0,0 +1,218 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.lang.ref.WeakReference; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; +import org.apache.qpid.AMQInvalidArgumentException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.filter.SelectorParsingException; +import org.apache.qpid.filter.selector.ParseException; +import org.apache.qpid.filter.selector.TokenMgrError; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.filter.JMSSelectorFilter; +import org.apache.qpid.server.filter.MessageFilter; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.Filterable; + +public class FilterSupport +{ + private static final Map> _selectorCache = + Collections.synchronizedMap(new WeakHashMap>()); + + static MessageFilter createJMSSelectorFilter(Map args) throws AMQInvalidArgumentException + { + final String selectorString = (String) args.get(AMQPFilterTypes.JMS_SELECTOR.toString()); + return getMessageFilter(selectorString); + } + + + private static MessageFilter getMessageFilter(String selectorString) throws AMQInvalidArgumentException + { + WeakReference selectorRef = _selectorCache.get(selectorString); + JMSSelectorFilter selector = null; + + if(selectorRef == null || (selector = selectorRef.get())==null) + { + try + { + selector = new JMSSelectorFilter(selectorString); + } + catch (ParseException e) + { + throw new AMQInvalidArgumentException("Cannot parse JMS selector \"" + selectorString + "\"", e); + } + catch (SelectorParsingException e) + { + throw new AMQInvalidArgumentException("Cannot parse JMS selector \"" + selectorString + "\"", e); + } + catch (TokenMgrError e) + { + throw new AMQInvalidArgumentException("Cannot parse JMS selector \"" + selectorString + "\"", e); + } + _selectorCache.put(selectorString, new WeakReference(selector)); + } + return selector; + } + + public static boolean argumentsContainFilter(final Map args) + { + return argumentsContainNoLocal(args) || argumentsContainJMSSelector(args); + } + + + public static void removeFilters(final Map args) + { + args.remove(AMQPFilterTypes.JMS_SELECTOR.toString()); + args.remove(AMQPFilterTypes.NO_LOCAL.toString()); + } + + + + static boolean argumentsContainNoLocal(final Map args) + { + return args != null + && args.containsKey(AMQPFilterTypes.NO_LOCAL.toString()) + && Boolean.TRUE.equals(args.get(AMQPFilterTypes.NO_LOCAL.toString())); + } + + static boolean argumentsContainJMSSelector(final Map args) + { + return args != null && (args.get(AMQPFilterTypes.JMS_SELECTOR.toString()) instanceof String) + && ((String)args.get(AMQPFilterTypes.JMS_SELECTOR.toString())).trim().length() != 0; + } + + static MessageFilter createMessageFilter(final Map args, AMQQueue queue) throws AMQInvalidArgumentException + { + if(argumentsContainNoLocal(args)) + { + MessageFilter filter = new NoLocalFilter(queue); + + if(argumentsContainJMSSelector(args)) + { + filter = new CompoundFilter(filter, createJMSSelectorFilter(args)); + } + return filter; + } + else + { + return createJMSSelectorFilter(args); + } + } + + static final class NoLocalFilter implements MessageFilter + { + private final AMQQueue _queue; + + public NoLocalFilter(AMQQueue queue) + { + _queue = queue; + } + + public boolean matches(Filterable message) + { + InboundMessage inbound = (InboundMessage) message; + final AMQSessionModel exclusiveOwningSession = _queue.getExclusiveOwningSession(); + return exclusiveOwningSession == null || !exclusiveOwningSession.onSameConnection(inbound); + + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + + if (o == null || getClass() != o.getClass()) + { + return false; + } + + NoLocalFilter that = (NoLocalFilter) o; + + return _queue == null ? that._queue == null : _queue.equals(that._queue); + } + + @Override + public int hashCode() + { + return _queue != null ? _queue.hashCode() : 0; + } + } + + static final class CompoundFilter implements MessageFilter + { + private MessageFilter _noLocalFilter; + private MessageFilter _jmsSelectorFilter; + + public CompoundFilter(MessageFilter filter, MessageFilter jmsSelectorFilter) + { + _noLocalFilter = filter; + _jmsSelectorFilter = jmsSelectorFilter; + } + + public boolean matches(Filterable message) + { + return _noLocalFilter.matches(message) && _jmsSelectorFilter.matches(message); + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + + CompoundFilter that = (CompoundFilter) o; + + if (_jmsSelectorFilter != null ? !_jmsSelectorFilter.equals(that._jmsSelectorFilter) : that._jmsSelectorFilter != null) + { + return false; + } + if (_noLocalFilter != null ? !_noLocalFilter.equals(that._noLocalFilter) : that._noLocalFilter != null) + { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + int result = _noLocalFilter != null ? _noLocalFilter.hashCode() : 0; + result = 31 * result + (_jmsSelectorFilter != null ? _jmsSelectorFilter.hashCode() : 0); + return result; + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java new file mode 100644 index 0000000000..eb4a84a5b9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java @@ -0,0 +1,284 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQInvalidArgumentException; +import org.apache.qpid.framing.AMQTypedValue; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.filter.MessageFilter; +import org.apache.qpid.server.message.AMQMessageHeader; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.queue.Filterable; + +/** + * Defines binding and matching based on a set of headers. + */ +class HeadersBinding +{ + private static final Logger _logger = Logger.getLogger(HeadersBinding.class); + + private final Map _mappings; + private final Binding _binding; + private final Set required = new HashSet(); + private final Map matches = new HashMap(); + private boolean matchAny; + private MessageFilter _filter; + + /** + * Creates a header 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 binding the binding to create a header binding using + */ + public HeadersBinding(Binding binding) + { + _binding = binding; + if(_binding !=null) + { + _mappings = _binding.getArguments(); + initMappings(); + } + else + { + _mappings = null; + } + } + + private void initMappings() + { + if(FilterSupport.argumentsContainFilter(_mappings)) + { + try + { + _filter = FilterSupport.createMessageFilter(_mappings,_binding.getQueue()); + } + catch (AMQInvalidArgumentException e) + { + _logger.warn("Invalid filter in binding queue '"+_binding.getQueue().getName() + +"' to exchange '"+_binding.getExchange().getName() + +"' with arguments: " + _binding.getArguments()); + _filter = new MessageFilter() + { + @Override + public boolean matches(Filterable message) + { + return false; + } + }; + } + } + for(Map.Entry entry : _mappings.entrySet()) + { + String propertyName = entry.getKey(); + Object value = entry.getValue(); + if (isSpecial(propertyName)) + { + processSpecial(propertyName, value); + } + else if (value == null || value.equals("")) + { + required.add(propertyName); + } + else + { + matches.put(propertyName,value); + } + } + } + + public Binding getBinding() + { + return _binding; + } + + /** + * 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(AMQMessageHeader headers) + { + if(headers == null) + { + return required.isEmpty() && matches.isEmpty(); + } + else + { + return matchAny ? or(headers) : and(headers); + } + } + + public boolean matches(InboundMessage message) + { + return matches(message.getMessageHeader()) && (_filter == null || _filter.matches(message)); + } + + private boolean and(AMQMessageHeader headers) + { + if(headers.containsHeaders(required)) + { + for(Map.Entry e : matches.entrySet()) + { + if(!e.getValue().equals(headers.getHeader(e.getKey()))) + { + return false; + } + } + return true; + } + else + { + return false; + } + } + + + private boolean or(final AMQMessageHeader headers) + { + if(required.isEmpty()) + { + return matches.isEmpty() || passesMatchesOr(headers); + } + else + { + if(!passesRequiredOr(headers)) + { + return !matches.isEmpty() && passesMatchesOr(headers); + } + else + { + return true; + } + + } + } + + private boolean passesMatchesOr(AMQMessageHeader headers) + { + for(Map.Entry entry : matches.entrySet()) + { + if(headers.containsHeader(entry.getKey()) + && ((entry.getValue() == null && headers.getHeader(entry.getKey()) == null) + || (entry.getValue().equals(headers.getHeader(entry.getKey()))))) + { + return true; + } + } + return false; + } + + private boolean passesRequiredOr(AMQMessageHeader headers) + { + for(String name : required) + { + if(headers.containsHeader(name)) + { + return true; + } + } + return false; + } + + 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-"); + } + + @Override + public boolean equals(final Object o) + { + if (this == o) + { + return true; + } + + if (o == null) + { + return false; + } + + if (!(o instanceof HeadersBinding)) + { + return false; + } + + final HeadersBinding hb = (HeadersBinding) o; + + if(_binding == null) + { + if(hb.getBinding() != null) + { + return false; + } + } + else if (!_binding.equals(hb.getBinding())) + { + return false; + } + + return true; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java new file mode 100644 index 0000000000..41dd7e010c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java @@ -0,0 +1,166 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * 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. + *

+ *

+ * 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
+ *  
+ */ +public class HeadersExchange extends AbstractExchange +{ + + private static final Logger _logger = Logger.getLogger(HeadersExchange.class); + + private final ConcurrentHashMap> _bindingsByKey = + new ConcurrentHashMap>(); + + private final CopyOnWriteArrayList _bindingHeaderMatchers = + new CopyOnWriteArrayList(); + + + public static final ExchangeType TYPE = new HeadersExchangeType(); + + public HeadersExchange() + { + super(TYPE); + } + + + public ArrayList doRoute(InboundMessage payload) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getName() + ": routing message with headers " + payload.getMessageHeader()); + } + + LinkedHashSet queues = new LinkedHashSet(); + + for (HeadersBinding hb : _bindingHeaderMatchers) + { + if (hb.matches(payload)) + { + Binding b = hb.getBinding(); + + b.incrementMatches(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getName() + ": delivering message with headers " + + payload.getMessageHeader() + " to " + b.getQueue().getName()); + } + queues.add(b.getQueue()); + } + } + + return new ArrayList(queues); + } + + protected void onBind(final Binding binding) + { + String bindingKey = binding.getBindingKey(); + AMQQueue queue = binding.getQueue(); + Map args = binding.getArguments(); + + assert queue != null; + assert bindingKey != null; + + CopyOnWriteArraySet bindings = _bindingsByKey.get(bindingKey); + + if(bindings == null) + { + bindings = new CopyOnWriteArraySet(); + CopyOnWriteArraySet newBindings; + if((newBindings = _bindingsByKey.putIfAbsent(bindingKey, bindings)) != null) + { + bindings = newBindings; + } + } + + if(_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getName() + ": Binding " + queue.getName() + + " with binding key '" +bindingKey + "' and args: " + args); + } + + _bindingHeaderMatchers.add(new HeadersBinding(binding)); + bindings.add(binding); + + } + + protected void onUnbind(final Binding binding) + { + assert binding != null; + + CopyOnWriteArraySet bindings = _bindingsByKey.get(binding.getBindingKey()); + if(bindings != null) + { + bindings.remove(binding); + } + + boolean removedBinding = _bindingHeaderMatchers.remove(new HeadersBinding(binding)); + if(_logger.isDebugEnabled()) + { + _logger.debug("Removing Binding: " + removedBinding); + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersExchangeType.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersExchangeType.java new file mode 100644 index 0000000000..42d04f5a97 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/HeadersExchangeType.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.exchange; + +import java.util.UUID; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class HeadersExchangeType implements ExchangeType +{ + @Override + public String getType() + { + return ExchangeDefaults.HEADERS_EXCHANGE_CLASS; + } + + public HeadersExchange newInstance(UUID id, VirtualHost host, String name, boolean durable, + boolean autoDelete) throws AMQException + { + HeadersExchange exch = new HeadersExchange(); + + exch.initialise(id, host, name, durable, autoDelete); + return exch; + } + + public String getDefaultExchangeName() + { + + return ExchangeDefaults.HEADERS_EXCHANGE_NAME; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java new file mode 100644 index 0000000000..6b8b84f5dd --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/TopicExchange.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.exchange; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQInvalidArgumentException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.exchange.topic.TopicExchangeResult; +import org.apache.qpid.server.exchange.topic.TopicMatcherResult; +import org.apache.qpid.server.exchange.topic.TopicNormalizer; +import org.apache.qpid.server.exchange.topic.TopicParser; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; + +public class TopicExchange extends AbstractExchange +{ + public static final ExchangeType TYPE = new TopicExchangeType(); + + + private static final Logger _logger = Logger.getLogger(TopicExchange.class); + + private final TopicParser _parser = new TopicParser(); + + private final Map _topicExchangeResults = + new ConcurrentHashMap(); + + private final Map> _bindings = new HashMap>(); + + public TopicExchange() + { + super(TYPE); + } + + protected synchronized void registerQueue(final Binding binding) throws AMQInvalidArgumentException + { + final String bindingKey = binding.getBindingKey(); + AMQQueue queue = binding.getQueue(); + Map args = binding.getArguments(); + + assert queue != null; + assert bindingKey != null; + + _logger.debug("Registering queue " + queue.getName() + " with routing key " + bindingKey); + + + String routingKey = TopicNormalizer.normalize(bindingKey); + + if(_bindings.containsKey(binding)) + { + Map oldArgs = _bindings.get(binding); + TopicExchangeResult result = _topicExchangeResults.get(routingKey); + + if(FilterSupport.argumentsContainFilter(args)) + { + if(FilterSupport.argumentsContainFilter(oldArgs)) + { + result.replaceQueueFilter(queue, + FilterSupport.createMessageFilter(oldArgs, queue), + FilterSupport.createMessageFilter(args, queue)); + } + else + { + result.addFilteredQueue(queue, FilterSupport.createMessageFilter(args, queue)); + result.removeUnfilteredQueue(queue); + } + } + else + { + if(FilterSupport.argumentsContainFilter(oldArgs)) + { + result.addUnfilteredQueue(queue); + result.removeFilteredQueue(queue, FilterSupport.createMessageFilter(oldArgs, queue)); + } + else + { + // TODO - fix control flow + return; + } + } + + result.addBinding(binding); + + } + else + { + + TopicExchangeResult result = _topicExchangeResults.get(routingKey); + if(result == null) + { + result = new TopicExchangeResult(); + if(FilterSupport.argumentsContainFilter(args)) + { + result.addFilteredQueue(queue, FilterSupport.createMessageFilter(args, queue)); + } + else + { + result.addUnfilteredQueue(queue); + } + _parser.addBinding(routingKey, result); + _topicExchangeResults.put(routingKey,result); + } + else + { + if(FilterSupport.argumentsContainFilter(args)) + { + result.addFilteredQueue(queue, FilterSupport.createMessageFilter(args, queue)); + } + else + { + result.addUnfilteredQueue(queue); + } + } + + result.addBinding(binding); + _bindings.put(binding, args); + } + + } + + + public ArrayList doRoute(InboundMessage payload) + { + + final String routingKey = payload.getRoutingKey() == null + ? "" + : payload.getRoutingKey(); + + final Collection matchedQueues = getMatchedQueues(payload, routingKey); + + ArrayList queues; + + if(matchedQueues.getClass() == ArrayList.class) + { + queues = (ArrayList) matchedQueues; + } + else + { + queues = new ArrayList(); + queues.addAll(matchedQueues); + } + + if(queues == null || queues.isEmpty()) + { + _logger.info("Message routing key: " + payload.getRoutingKey() + " No routes."); + } + + return queues; + + } + + private boolean deregisterQueue(final Binding binding) + { + if(_bindings.containsKey(binding)) + { + Map bindingArgs = _bindings.remove(binding); + String bindingKey = TopicNormalizer.normalize(binding.getBindingKey()); + TopicExchangeResult result = _topicExchangeResults.get(bindingKey); + + result.removeBinding(binding); + + if(FilterSupport.argumentsContainFilter(bindingArgs)) + { + try + { + result.removeFilteredQueue(binding.getQueue(), FilterSupport.createMessageFilter(bindingArgs, + binding.getQueue())); + } + catch (AMQInvalidArgumentException e) + { + return false; + } + } + else + { + result.removeUnfilteredQueue(binding.getQueue()); + } + return true; + } + else + { + return false; + } + } + + private Collection getMatchedQueues(InboundMessage message, String routingKey) + { + + Collection results = _parser.parse(routingKey); + switch(results.size()) + { + case 0: + return Collections.EMPTY_SET; + case 1: + TopicMatcherResult[] resultQueues = new TopicMatcherResult[1]; + results.toArray(resultQueues); + return ((TopicExchangeResult)resultQueues[0]).processMessage(message, null); + default: + Collection queues = new HashSet(); + for(TopicMatcherResult result : results) + { + TopicExchangeResult res = (TopicExchangeResult)result; + + for(Binding b : res.getBindings()) + { + b.incrementMatches(); + } + + queues = res.processMessage(message, queues); + } + return queues; + } + + + } + + protected void onBind(final Binding binding) + { + try + { + registerQueue(binding); + } + catch (AMQInvalidArgumentException e) + { + throw new RuntimeException(e); + } + } + + protected void onUnbind(final Binding binding) + { + deregisterQueue(binding); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/TopicExchangeType.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/TopicExchangeType.java new file mode 100644 index 0000000000..3bbae700be --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/TopicExchangeType.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.exchange; + +import java.util.UUID; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class TopicExchangeType implements ExchangeType +{ + @Override + public String getType() + { + return ExchangeDefaults.TOPIC_EXCHANGE_CLASS; + } + + public TopicExchange newInstance(UUID id, VirtualHost host, + String name, + boolean durable, + boolean autoDelete) throws AMQException + { + TopicExchange exch = new TopicExchange(); + exch.initialise(id, host, name, durable, autoDelete); + return exch; + } + + public String getDefaultExchangeName() + { + return ExchangeDefaults.TOPIC_EXCHANGE_NAME; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicExchangeResult.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicExchangeResult.java new file mode 100644 index 0000000000..44d5f7f1d0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicExchangeResult.java @@ -0,0 +1,209 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.topic; + +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.filter.MessageFilter; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.queue.AMQQueue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public final class TopicExchangeResult implements TopicMatcherResult +{ + private final List _bindings = new CopyOnWriteArrayList(); + private final Map _unfilteredQueues = new ConcurrentHashMap(); + private final ConcurrentHashMap> _filteredQueues = new ConcurrentHashMap>(); + private volatile ArrayList _unfilteredQueueList = new ArrayList(0); + + public void addUnfilteredQueue(AMQQueue queue) + { + Integer instances = _unfilteredQueues.get(queue); + if(instances == null) + { + _unfilteredQueues.put(queue, 1); + ArrayList newList = new ArrayList(_unfilteredQueueList); + newList.add(queue); + _unfilteredQueueList = newList; + } + else + { + _unfilteredQueues.put(queue, instances + 1); + } + } + + public void removeUnfilteredQueue(AMQQueue queue) + { + Integer instances = _unfilteredQueues.get(queue); + if(instances == 1) + { + _unfilteredQueues.remove(queue); + ArrayList newList = new ArrayList(_unfilteredQueueList); + newList.remove(queue); + _unfilteredQueueList = newList; + + } + else + { + _unfilteredQueues.put(queue,instances - 1); + } + + } + + public Collection getUnfilteredQueues() + { + return _unfilteredQueues.keySet(); + } + + public void addBinding(Binding binding) + { + _bindings.add(binding); + } + + public void removeBinding(Binding binding) + { + _bindings.remove(binding); + } + + public List getBindings() + { + return new ArrayList(_bindings); + } + + public void addFilteredQueue(AMQQueue queue, MessageFilter filter) + { + Map filters = _filteredQueues.get(queue); + if(filters == null) + { + filters = new ConcurrentHashMap(); + _filteredQueues.put(queue, filters); + } + Integer instances = filters.get(filter); + if(instances == null) + { + filters.put(filter,1); + } + else + { + filters.put(filter, instances + 1); + } + + } + + public void removeFilteredQueue(AMQQueue queue, MessageFilter filter) + { + Map filters = _filteredQueues.get(queue); + if(filters != null) + { + Integer instances = filters.get(filter); + if(instances != null) + { + if(instances == 1) + { + filters.remove(filter); + if(filters.isEmpty()) + { + _filteredQueues.remove(queue); + } + } + else + { + filters.put(filter, instances - 1); + } + } + + } + + } + + public void replaceQueueFilter(AMQQueue queue, + MessageFilter oldFilter, + MessageFilter newFilter) + { + Map filters = _filteredQueues.get(queue); + Map newFilters = new ConcurrentHashMap(filters); + Integer oldFilterInstances = filters.get(oldFilter); + if(oldFilterInstances == 1) + { + newFilters.remove(oldFilter); + } + else + { + newFilters.put(oldFilter, oldFilterInstances-1); + } + Integer newFilterInstances = filters.get(newFilter); + if(newFilterInstances == null) + { + newFilters.put(newFilter, 1); + } + else + { + newFilters.put(newFilter, newFilterInstances+1); + } + _filteredQueues.put(queue,newFilters); + } + + public Collection processMessage(InboundMessage msg, Collection queues) + { + if(queues == null) + { + if(_filteredQueues.isEmpty()) + { + return _unfilteredQueueList; + } + else + { + queues = new HashSet(); + } + } + else if(!(queues instanceof Set)) + { + queues = new HashSet(queues); + } + + queues.addAll(_unfilteredQueues.keySet()); + if(!_filteredQueues.isEmpty()) + { + for(Map.Entry> entry : _filteredQueues.entrySet()) + { + if(!queues.contains(entry.getKey())) + { + for(MessageFilter filter : entry.getValue().keySet()) + { + if(filter.matches(msg)) + { + queues.add(entry.getKey()); + } + } + } + } + } + return queues; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java new file mode 100644 index 0000000000..85338c0760 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java @@ -0,0 +1,310 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.topic; + +import java.util.Arrays; +import java.util.Iterator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; + +public class TopicMatcherDFAState +{ + private static final AtomicInteger stateId = new AtomicInteger(); + + private final int _id = stateId.incrementAndGet(); + + private final Collection _results; + private final Map _nextStateMap; + private static final String TOPIC_DELIMITTER = "\\."; + + + public TopicMatcherDFAState(Map nextStateMap, + Collection results ) + { + _nextStateMap = nextStateMap; + _results = results; + } + + + public TopicMatcherDFAState nextState(TopicWord word) + { + final TopicMatcherDFAState nextState = _nextStateMap.get(word); + return nextState == null ? _nextStateMap.get(TopicWord.ANY_WORD) : nextState; + } + + public Collection terminate() + { + return _results; + } + + + public Collection parse(TopicWordDictionary dictionary, String routingKey) + { + return parse(dictionary, Arrays.asList(routingKey.split(TOPIC_DELIMITTER)).iterator()); + } + + private Collection parse(final TopicWordDictionary dictionary, + final Iterator tokens) + { + if(!tokens.hasNext()) + { + return _results; + } + TopicWord word = dictionary.getWord(tokens.next()); + TopicMatcherDFAState nextState = _nextStateMap.get(word); + if(nextState == null && word != TopicWord.ANY_WORD) + { + nextState = _nextStateMap.get(TopicWord.ANY_WORD); + } + if(nextState == null) + { + return Collections.EMPTY_LIST; + } + // Shortcut if we are at a looping terminal state + if((nextState == this) && (_nextStateMap.size() == 1) && _nextStateMap.containsKey(TopicWord.ANY_WORD)) + { + return _results; + } + + return nextState.parse(dictionary, tokens); + + } + + + public TopicMatcherDFAState mergeStateMachines(TopicMatcherDFAState otherStateMachine) + { + Map, TopicMatcherDFAState> newStateMap= new HashMap, TopicMatcherDFAState>(); + + Collection results; + + if(_results.isEmpty()) + { + results = otherStateMachine._results; + } + else if(otherStateMachine._results.isEmpty()) + { + results = _results; + } + else + { + results = new HashSet(_results); + results.addAll(otherStateMachine._results); + } + + + final Map newNextStateMap = new HashMap(); + + TopicMatcherDFAState newState = new TopicMatcherDFAState(newNextStateMap, results); + + + Set oldStates = new HashSet(); + oldStates.add(this); + oldStates.add(otherStateMachine); + + newStateMap.put(oldStates, newState); + + mergeStateMachines(oldStates, newNextStateMap, newStateMap); + + return newState; + + } + + private static void mergeStateMachines( + final Set oldStates, + final Map newNextStateMap, + final Map, TopicMatcherDFAState> newStateMap) + { + Map> nfaMap = new HashMap>(); + + for(TopicMatcherDFAState state : oldStates) + { + Map map = state._nextStateMap; + for(Map.Entry entry : map.entrySet()) + { + Set states = nfaMap.get(entry.getKey()); + if(states == null) + { + states = new HashSet(); + nfaMap.put(entry.getKey(), states); + } + states.add(entry.getValue()); + } + } + + Set anyWordStates = nfaMap.get(TopicWord.ANY_WORD); + + for(Map.Entry> transition : nfaMap.entrySet()) + { + Set destinations = transition.getValue(); + + if(anyWordStates != null) + { + destinations.addAll(anyWordStates); + } + + TopicMatcherDFAState nextState = newStateMap.get(destinations); + if(nextState == null) + { + + if(destinations.size() == 1) + { + nextState = destinations.iterator().next(); + newStateMap.put(destinations, nextState); + } + else + { + Collection results; + + Set> resultSets = new HashSet>(); + for(TopicMatcherDFAState destination : destinations) + { + resultSets.add(destination._results); + } + resultSets.remove(Collections.EMPTY_SET); + if(resultSets.size() == 0) + { + results = Collections.EMPTY_SET; + } + else if(resultSets.size() == 1) + { + results = resultSets.iterator().next(); + } + else + { + results = new HashSet(); + for(Collection oldResult : resultSets) + { + results.addAll(oldResult); + } + } + + final Map nextStateMap = new HashMap(); + + nextState = new TopicMatcherDFAState(nextStateMap, results); + newStateMap.put(destinations, nextState); + + mergeStateMachines( + destinations, + nextStateMap, + newStateMap); + + + } + + + } + newNextStateMap.put(transition.getKey(),nextState); + } + + // Remove redundant transitions where defined tokenWord has same action as ANY_WORD + TopicMatcherDFAState anyWordState = newNextStateMap.get(TopicWord.ANY_WORD); + if(anyWordState != null) + { + List removeList = new ArrayList(); + for(Map.Entry entry : newNextStateMap.entrySet()) + { + if(entry.getValue() == anyWordState && entry.getKey() != TopicWord.ANY_WORD) + { + removeList.add(entry.getKey()); + } + } + for(TopicWord removeKey : removeList) + { + newNextStateMap.remove(removeKey); + } + } + + + + } + + + public String toString() + { + StringBuilder transitions = new StringBuilder(); + for(Map.Entry entry : _nextStateMap.entrySet()) + { + transitions.append("[ "); + transitions.append(entry.getKey()); + transitions.append("\t ->\t "); + transitions.append(entry.getValue().getId()); + transitions.append(" ]\n"); + } + + + return "[ State " + getId() + " ]\n" + transitions + "\n"; + + } + + public String reachableStates() + { + StringBuilder result = new StringBuilder("Start state: " + getId() + "\n"); + + SortedSet reachableStates = + new TreeSet(new Comparator() + { + public int compare(final TopicMatcherDFAState o1, final TopicMatcherDFAState o2) + { + return o1.getId() - o2.getId(); + } + }); + reachableStates.add(this); + + int count; + + do + { + count = reachableStates.size(); + Collection originalStates = new ArrayList(reachableStates); + for(TopicMatcherDFAState state : originalStates) + { + reachableStates.addAll(state._nextStateMap.values()); + } + } + while(reachableStates.size() != count); + + + + for(TopicMatcherDFAState state : reachableStates) + { + result.append(state.toString()); + } + + return result.toString(); + } + + + int getId() + { + return _id; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java new file mode 100644 index 0000000000..6084b18b31 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.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.exchange.topic; + +public interface TopicMatcherResult +{ +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicNormalizer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicNormalizer.java new file mode 100644 index 0000000000..e45d6a539d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicNormalizer.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.exchange.topic; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.ArrayList; +import java.util.List; + +public class TopicNormalizer +{ + + private static final String STAR_TOKEN = "*"; + private static final String HASH_TOKEN = "#"; + private static final String SEPARATOR = "."; + + + private TopicNormalizer() + { + } + + public static String normalize(String routingKey) + { + if(routingKey == null) + { + return ""; + } + else if(!(routingKey.contains(HASH_TOKEN) || !routingKey.contains(STAR_TOKEN))) + { + return routingKey; + } + else + { + List subscriptionList = new ArrayList(Arrays.asList(routingKey.split("\\."))); + + 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(HASH_TOKEN)) + { + if (subscriptionList.get(index + 1).equals(HASH_TOKEN)) + { + // we don't need #.# delete this one + subscriptionList.remove(index); + size--; + // redo this normalisation + index--; + } + + if (subscriptionList.get(index + 1).equals(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 + } + + Iterator iter = subscriptionList.iterator(); + StringBuilder builder = new StringBuilder(iter.next()); + while(iter.hasNext()) + { + builder.append(SEPARATOR).append(iter.next()); + } + return builder.toString(); + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java new file mode 100644 index 0000000000..214ca23b49 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java @@ -0,0 +1,456 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.topic; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +public class TopicParser +{ + private static final String TOPIC_DELIMITER = "\\."; + + private final TopicWordDictionary _dictionary = new TopicWordDictionary(); + private final AtomicReference _stateMachine = new AtomicReference(); + + private static class Position + { + private final TopicWord _word; + private final boolean _selfTransition; + private final int _position; + private final boolean _endState; + private boolean _followedByAnyLoop; + + + public Position(final int position, final TopicWord word, final boolean selfTransition, final boolean endState) + { + _position = position; + _word = word; + _selfTransition = selfTransition; + _endState = endState; + } + + + public TopicWord getWord() + { + return _word; + } + + public boolean isSelfTransition() + { + return _selfTransition; + } + + public int getPosition() + { + return _position; + } + + public boolean isEndState() + { + return _endState; + } + + public boolean isFollowedByAnyLoop() + { + return _followedByAnyLoop; + } + + public void setFollowedByAnyLoop(boolean followedByAnyLoop) + { + _followedByAnyLoop = followedByAnyLoop; + } + } + + private static final Position ERROR_POSITION = new Position(Integer.MAX_VALUE,null, true, false); + + private static class SimpleState + { + private Set _positions; + private Map _nextState; + } + + + public void addBinding(String bindingKey, TopicMatcherResult result) + { + + TopicMatcherDFAState startingStateMachine; + TopicMatcherDFAState newStateMachine; + + do + { + startingStateMachine = _stateMachine.get(); + if(startingStateMachine == null) + { + newStateMachine = createStateMachine(bindingKey, result); + } + else + { + newStateMachine = startingStateMachine.mergeStateMachines(createStateMachine(bindingKey, result)); + } + + } + while(!_stateMachine.compareAndSet(startingStateMachine,newStateMachine)); + + } + + public Collection parse(String routingKey) + { + TopicMatcherDFAState stateMachine = _stateMachine.get(); + if(stateMachine == null) + { + return Collections.EMPTY_SET; + } + else + { + return stateMachine.parse(_dictionary,routingKey); + } + } + + + TopicMatcherDFAState createStateMachine(String bindingKey, TopicMatcherResult result) + { + List wordList = createTopicWordList(bindingKey); + int wildCards = 0; + for(TopicWord word : wordList) + { + if(word == TopicWord.WILDCARD_WORD) + { + wildCards++; + } + } + if(wildCards == 0) + { + TopicMatcherDFAState[] states = new TopicMatcherDFAState[wordList.size()+1]; + states[states.length-1] = new TopicMatcherDFAState(Collections.EMPTY_MAP, Collections.singleton(result)); + for(int i = states.length-2; i >= 0; i--) + { + states[i] = new TopicMatcherDFAState(Collections.singletonMap(wordList.get(i),states[i+1]),Collections.EMPTY_SET); + + } + return states[0]; + } + else if(wildCards == wordList.size()) + { + Map stateMap = new HashMap(); + TopicMatcherDFAState state = new TopicMatcherDFAState(stateMap, Collections.singleton(result)); + stateMap.put(TopicWord.ANY_WORD, state); + return state; + } + + + int positionCount = wordList.size() - wildCards; + + Position[] positions = new Position[positionCount+1]; + + int lastWord; + + if(wordList.get(wordList.size()-1)== TopicWord.WILDCARD_WORD) + { + lastWord = wordList.size()-1; + positions[positionCount] = new Position(positionCount, TopicWord.ANY_WORD, true, true); + } + else + { + lastWord = wordList.size(); + positions[positionCount] = new Position(positionCount, TopicWord.ANY_WORD, false, true); + } + + + int pos = 0; + int wordPos = 0; + + + while(wordPos < lastWord) + { + TopicWord word = wordList.get(wordPos++); + + if(word == TopicWord.WILDCARD_WORD) + { + int nextWordPos = wordPos++; + word = wordList.get(nextWordPos); + + positions[pos] = new Position(pos++,word,true,false); + } + else + { + positions[pos] = new Position(pos++,word,false,false); + } + + } + + + for(int p = 0; p,SimpleState> stateMap = new HashMap,SimpleState>(); + + + SimpleState state = new SimpleState(); + state._positions = Collections.singleton( positions[0] ); + stateMap.put(state._positions, state); + + calculateNextStates(state, stateMap, positions); + + SimpleState[] simpleStates = stateMap.values().toArray(new SimpleState[stateMap.size()]); + HashMap[] dfaStateMaps = new HashMap[simpleStates.length]; + Map simple2DFAMap = new HashMap(); + + for(int i = 0; i < simpleStates.length; i++) + { + + Collection results; + boolean endState = false; + + for(Position p : simpleStates[i]._positions) + { + if(p.isEndState()) + { + endState = true; + break; + } + } + + if(endState) + { + results = Collections.singleton(result); + } + else + { + results = Collections.EMPTY_SET; + } + + dfaStateMaps[i] = new HashMap(); + simple2DFAMap.put(simpleStates[i], new TopicMatcherDFAState(dfaStateMaps[i],results)); + + } + for(int i = 0; i < simpleStates.length; i++) + { + SimpleState simpleState = simpleStates[i]; + + Map nextSimpleStateMap = simpleState._nextState; + for(Map.Entry stateMapEntry : nextSimpleStateMap.entrySet()) + { + dfaStateMaps[i].put(stateMapEntry.getKey(), simple2DFAMap.get(stateMapEntry.getValue())); + } + + } + + return simple2DFAMap.get(state); + + } + + + + private void calculateNextStates(final SimpleState state, + final Map, SimpleState> stateMap, + final Position[] positions) + { + Map> transitions = new HashMap>(); + + for(Position pos : state._positions) + { + if(pos.isSelfTransition()) + { + Set dest = transitions.get(TopicWord.ANY_WORD); + if(dest == null) + { + dest = new HashSet(); + transitions.put(TopicWord.ANY_WORD,dest); + } + dest.add(pos); + } + + final int nextPos = pos.getPosition() + 1; + Position nextPosition = nextPos == positions.length ? ERROR_POSITION : positions[nextPos]; + + Set dest = transitions.get(pos.getWord()); + if(dest == null) + { + dest = new HashSet(); + transitions.put(pos.getWord(),dest); + } + dest.add(nextPosition); + + } + + Set anyWordTransitions = transitions.get(TopicWord.ANY_WORD); + if(anyWordTransitions != null) + { + for(Set dest : transitions.values()) + { + dest.addAll(anyWordTransitions); + } + } + + state._nextState = new HashMap(); + + for(Map.Entry> dest : transitions.entrySet()) + { + + if(dest.getValue().size()>1) + { + dest.getValue().remove(ERROR_POSITION); + } + Position loopingTerminal = null; + for(Position destPos : dest.getValue()) + { + if(destPos.isSelfTransition() && destPos.isEndState()) + { + loopingTerminal = destPos; + break; + } + } + + if(loopingTerminal!=null) + { + dest.setValue(Collections.singleton(loopingTerminal)); + } + else + { + Position anyLoop = null; + for(Position destPos : dest.getValue()) + { + if(destPos.isFollowedByAnyLoop()) + { + if(anyLoop == null || anyLoop.getPosition() < destPos.getPosition()) + { + anyLoop = destPos; + } + } + } + if(anyLoop != null) + { + Collection removals = new ArrayList(); + for(Position destPos : dest.getValue()) + { + if(destPos.getPosition() < anyLoop.getPosition()) + { + removals.add(destPos); + } + } + dest.getValue().removeAll(removals); + } + } + + SimpleState stateForEntry = stateMap.get(dest.getValue()); + if(stateForEntry == null) + { + stateForEntry = new SimpleState(); + stateForEntry._positions = dest.getValue(); + stateMap.put(dest.getValue(),stateForEntry); + calculateNextStates(stateForEntry, + stateMap, + positions); + } + state._nextState.put(dest.getKey(),stateForEntry); + + + + } + + // remove redundant transitions + SimpleState anyWordState = state._nextState.get(TopicWord.ANY_WORD); + if(anyWordState != null) + { + List removeList = new ArrayList(); + for(Map.Entry entry : state._nextState.entrySet()) + { + if(entry.getValue() == anyWordState && entry.getKey() != TopicWord.ANY_WORD) + { + removeList.add(entry.getKey()); + } + } + for(TopicWord removeKey : removeList) + { + state._nextState.remove(removeKey); + } + } + + + } + + private List createTopicWordList(final String bindingKey) + { + String[] tokens = bindingKey.split(TOPIC_DELIMITER); + TopicWord previousWord = null; + + List wordList = new ArrayList(); + + for(String token : tokens) + { + TopicWord nextWord = _dictionary.getOrCreateWord(token); + if(previousWord == TopicWord.WILDCARD_WORD) + { + + if(nextWord == TopicWord.WILDCARD_WORD) + { + // consecutive wildcards can be merged + // i.e. subsequent wildcards can be discarded + continue; + } + else if(nextWord == TopicWord.ANY_WORD) + { + // wildcard and anyword can be reordered to always put anyword first + wordList.set(wordList.size()-1,TopicWord.ANY_WORD); + nextWord = TopicWord.WILDCARD_WORD; + } + } + wordList.add(nextWord); + previousWord = nextWord; + + } + return wordList; + } + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java new file mode 100644 index 0000000000..c905299733 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java @@ -0,0 +1,39 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ + +package org.apache.qpid.server.exchange.topic; + +public final class TopicWord +{ + public static final TopicWord ANY_WORD = new TopicWord("*"); + public static final TopicWord WILDCARD_WORD = new TopicWord("#"); + private String _word; + + public TopicWord(String s) + { + _word = s; + } + + public String toString() + { + return _word; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java new file mode 100644 index 0000000000..24c41ee7da --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.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.exchange.topic; + +import java.util.concurrent.ConcurrentHashMap; + +public class TopicWordDictionary +{ + private final ConcurrentHashMap _dictionary = + new ConcurrentHashMap(); + + public TopicWordDictionary() + { + _dictionary.put("*", TopicWord.ANY_WORD); + _dictionary.put("#", TopicWord.WILDCARD_WORD); + } + + public TopicWord getOrCreateWord(String name) + { + TopicWord word = _dictionary.putIfAbsent(name, new TopicWord(name)); + if(word == null) + { + word = _dictionary.get(name); + } + return word; + } + + + public TopicWord getWord(String name) + { + TopicWord word = _dictionary.get(name); + if(word == null) + { + word = TopicWord.ANY_WORD; + } + return word; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/FilterManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/FilterManager.java new file mode 100644 index 0000000000..b5e282038b --- /dev/null +++ b/qpid/java/broker-core/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 +// + +import org.apache.qpid.server.queue.Filterable; + +public interface FilterManager +{ + void add(MessageFilter filter); + + void remove(MessageFilter filter); + + boolean allAllow(Filterable msg); + + boolean hasFilters(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java new file mode 100644 index 0000000000..07049a6c97 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server.filter; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInvalidArgumentException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.filter.SelectorParsingException; +import org.apache.qpid.filter.selector.ParseException; +import org.apache.qpid.filter.selector.TokenMgrError; +import org.apache.qpid.framing.FieldTable; + +import java.util.Map; + + +public class FilterManagerFactory +{ + + private final static Logger _logger = Logger.getLogger(FilterManagerFactory.class); + + private FilterManagerFactory() + { + } + + //fixme move to a common class so it can be refered to from client code. + + public static FilterManager createManager(Map filters) throws AMQException + { + FilterManager manager = null; + + if (filters != null) + { + + if(filters.containsKey(AMQPFilterTypes.JMS_SELECTOR.toString())) + { + Object selector = filters.get(AMQPFilterTypes.JMS_SELECTOR.toString()); + + if (selector instanceof String && !selector.equals("")) + { + manager = new SimpleFilterManager(); + try + { + manager.add(new JMSSelectorFilter((String)selector)); + } + catch (ParseException e) + { + throw new AMQInvalidArgumentException("Cannot parse JMS selector \"" + selector + "\"", e); + } + catch (SelectorParsingException e) + { + throw new AMQInvalidArgumentException("Cannot parse JMS selector \"" + selector + "\"", e); + } + catch (TokenMgrError e) + { + throw new AMQInvalidArgumentException("Cannot parse JMS selector \"" + selector + "\"", e); + } + } + + } + + + } + else + { + _logger.debug("No Filters found."); + } + + + return manager; + + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java new file mode 100644 index 0000000000..3d0d9a0f31 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.apache.log4j.Logger; +import org.apache.qpid.filter.BooleanExpression; +import org.apache.qpid.filter.FilterableMessage; +import org.apache.qpid.filter.SelectorParsingException; +import org.apache.qpid.filter.selector.ParseException; +import org.apache.qpid.filter.selector.SelectorParser; +import org.apache.qpid.filter.selector.TokenMgrError; +import org.apache.qpid.server.queue.Filterable; + + +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 ParseException, TokenMgrError, SelectorParsingException + { + _selector = selector; + _matcher = new SelectorParser().parse(selector); + } + + public boolean matches(Filterable message) + { + + boolean match = _matcher.matches(wrap(message)); + if(_logger.isDebugEnabled()) + { + _logger.debug(message + " match(" + match + ") selector(" + System.identityHashCode(_selector) + "):" + _selector); + } + return match; + } + + private FilterableMessage wrap(final Filterable message) + { + return new FilterableMessage() + { + public boolean isPersistent() + { + return message.isPersistent(); + } + + public boolean isRedelivered() + { + return message.isRedelivered(); + } + + public Object getHeader(String name) + { + return message.getMessageHeader().getHeader(name); + } + + public String getReplyTo() + { + return message.getMessageHeader().getReplyTo(); + } + + public String getType() + { + return message.getMessageHeader().getType(); + } + + public byte getPriority() + { + return message.getMessageHeader().getPriority(); + } + + public String getMessageId() + { + return message.getMessageHeader().getMessageId(); + } + + public long getTimestamp() + { + return message.getMessageHeader().getTimestamp(); + } + + public String getCorrelationId() + { + return message.getMessageHeader().getCorrelationId(); + } + + public long getExpiration() + { + return message.getMessageHeader().getExpiration(); + } + }; + } + + public String getSelector() + { + return _selector; + } + + @Override + public String toString() + { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("selector", _selector) + .toString(); + } + + @Override + public int hashCode() + { + return new HashCodeBuilder().append(_selector).toHashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + if (obj == this) + { + return true; + } + if (obj.getClass() != getClass()) + { + return false; + } + JMSSelectorFilter rhs = (JMSSelectorFilter) obj; + return new EqualsBuilder().append(_selector, rhs._selector).isEquals(); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/MessageFilter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/MessageFilter.java new file mode 100644 index 0000000000..f5416af09a --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/MessageFilter.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server.filter; + +import org.apache.qpid.server.queue.Filterable; + +public interface MessageFilter +{ + boolean matches(Filterable message); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java new file mode 100644 index 0000000000..d3e097d22c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server.filter; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.Filterable; + +public class NoConsumerFilter implements MessageFilter +{ + private final static Logger _logger = org.apache.log4j.Logger.getLogger(NoConsumerFilter.class); + + + public NoConsumerFilter() throws AMQException + { + _logger.info("Created NoConsumerFilter"); + } + + public boolean matches(Filterable message) + { + return true; + } + + @Override + public String toString() + { + return "NoConsumer"; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java new file mode 100644 index 0000000000..6c158de8b5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.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.filter; + +import org.apache.log4j.Logger; + +import org.apache.qpid.server.queue.Filterable; + +import java.util.concurrent.ConcurrentLinkedQueue; + +public class SimpleFilterManager implements FilterManager +{ + private final Logger _logger = Logger.getLogger(SimpleFilterManager.class); + + private final ConcurrentLinkedQueue _filters; + private String _toString = ""; + + public SimpleFilterManager() + { + _logger.debug("Creating SimpleFilterManager"); + _filters = new ConcurrentLinkedQueue(); + } + + public SimpleFilterManager(JMSSelectorFilter messageFilter) + { + this(); + add(messageFilter); + } + + public void add(MessageFilter filter) + { + _filters.add(filter); + updateStringValue(); + } + + public void remove(MessageFilter filter) + { + _filters.remove(filter); + updateStringValue(); + } + + public boolean allAllow(Filterable msg) + { + for (MessageFilter filter : _filters) + { + if (!filter.matches(msg)) + { + return false; + } + } + return true; + } + + public boolean hasFilters() + { + return !_filters.isEmpty(); + } + + + @Override + public String toString() + { + return _toString; + } + + private void updateStringValue() + { + StringBuilder toString = new StringBuilder(); + for (MessageFilter filter : _filters) + { + toString.append(filter.toString()); + toString.append(","); + } + + if (_filters.size() > 0) + { + //Remove the last ',' + toString.deleteCharAt(toString.length()-1); + } + _toString = toString.toString(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java new file mode 100644 index 0000000000..124fb0d1d9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java @@ -0,0 +1,74 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.flow; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class AbstractFlowCreditManager implements FlowCreditManager +{ + private final AtomicBoolean _suspended = new AtomicBoolean(false); + private final ArrayList _listeners = new ArrayList(); + + public final void addStateListener(FlowCreditManagerListener listener) + { + synchronized(_listeners) + { + if(!_listeners.contains(listener)) + { + _listeners.add(listener); + } + } + } + + public final boolean removeListener(FlowCreditManagerListener listener) + { + synchronized(_listeners) + { + return _listeners.remove(listener); + } + } + + private void notifyListeners(final boolean suspended) + { + synchronized(_listeners) + { + final int size = _listeners.size(); + for(int i = 0; i 0L; + } + + public boolean useCreditForMessage(long msgSize) + { + if(hasCredit()) + { + if(_bytesCredit.addAndGet(-msgSize) >= 0) + { + return true; + } + else + { + _bytesCredit.addAndGet(msgSize); + setSuspended(true); + return false; + } + } + else + { + return false; + } + + } + + public void setBytesCredit(long bytesCredit) + { + _bytesCredit.set( bytesCredit ); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java new file mode 100644 index 0000000000..280f2851a4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.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.flow; + + + +public interface FlowCreditManager +{ + long getMessageCredit(); + + long getBytesCredit(); + + public static interface FlowCreditManagerListener + { + void creditStateChanged(boolean hasCredit); + } + + void addStateListener(FlowCreditManagerListener listener); + + boolean removeListener(FlowCreditManagerListener listener); + + public void restoreCredit(long messageCredit, long bytesCredit); + + public boolean hasCredit(); + + public boolean useCreditForMessage(long msgSize); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java new file mode 100644 index 0000000000..89fc60666b --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.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.flow; + + +public class LimitlessCreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + public long getMessageCredit() + { + return -1L; + } + + public long getBytesCredit() + { + return -1L; + } + + public void restoreCredit(long messageCredit, long bytesCredit) + { + } + + public void removeAllCredit() + { + } + + public boolean hasCredit() + { + return true; + } + + public boolean useCreditForMessage(long msgSize) + { + return true; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java new file mode 100644 index 0000000000..31c1fda968 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.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.flow; + + +public class MessageAndBytesCreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + private long _messageCredit; + private long _bytesCredit; + + public MessageAndBytesCreditManager(final long messageCredit, final long bytesCredit) + { + _messageCredit = messageCredit; + _bytesCredit = bytesCredit; + } + + public synchronized long getMessageCredit() + { + return _messageCredit; + } + + public synchronized long getBytesCredit() + { + return _bytesCredit; + } + + public synchronized void restoreCredit(long messageCredit, long bytesCredit) + { + _messageCredit += messageCredit; + _bytesCredit += bytesCredit; + setSuspended(hasCredit()); + } + + public synchronized void removeAllCredit() + { + _messageCredit = 0L; + _bytesCredit = 0L; + setSuspended(true); + } + + public synchronized boolean hasCredit() + { + return (_messageCredit > 0L) && ( _bytesCredit > 0L ); + } + + public synchronized boolean useCreditForMessage(final long msgSize) + { + if(_messageCredit == 0L) + { + setSuspended(true); + return false; + } + else + { + if(msgSize > _bytesCredit) + { + setSuspended(true); + return false; + } + _messageCredit--; + _bytesCredit -= msgSize; + setSuspended(false); + return true; + } + + } + + public synchronized void setBytesCredit(long bytesCredit) + { + _bytesCredit = bytesCredit; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java new file mode 100644 index 0000000000..1817e8ad31 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.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.flow; + +import java.util.concurrent.atomic.AtomicLong; + +public class MessageOnlyCreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + private final AtomicLong _messageCredit; + + public MessageOnlyCreditManager(final long initialCredit) + { + _messageCredit = new AtomicLong(initialCredit); + } + + public long getMessageCredit() + { + return _messageCredit.get(); + } + + public long getBytesCredit() + { + return -1L; + } + + public void restoreCredit(long messageCredit, long bytesCredit) + { + _messageCredit.addAndGet(messageCredit); + setSuspended(false); + + } + + public void removeAllCredit() + { + setSuspended(true); + _messageCredit.set(0L); + } + + public boolean hasCredit() + { + return _messageCredit.get() > 0L; + } + + public boolean useCreditForMessage(long msgSize) + { + if(hasCredit()) + { + if(_messageCredit.addAndGet(-1L) >= 0) + { + setSuspended(false); + return true; + } + else + { + _messageCredit.addAndGet(1L); + setSuspended(true); + return false; + } + } + else + { + setSuspended(true); + return false; + } + + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java new file mode 100644 index 0000000000..fc2d4bfb53 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java @@ -0,0 +1,192 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.flow; + + +public class Pre0_10CreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + + private volatile long _bytesCreditLimit; + private volatile long _messageCreditLimit; + + private volatile long _bytesCredit; + private volatile long _messageCredit; + + public Pre0_10CreditManager(long bytesCreditLimit, long messageCreditLimit) + { + _bytesCreditLimit = bytesCreditLimit; + _messageCreditLimit = messageCreditLimit; + _bytesCredit = bytesCreditLimit; + _messageCredit = messageCreditLimit; + } + + + public synchronized void setCreditLimits(final long bytesCreditLimit, final long messageCreditLimit) + { + long bytesCreditChange = bytesCreditLimit - _bytesCreditLimit; + long messageCreditChange = messageCreditLimit - _messageCreditLimit; + + + + if(bytesCreditChange != 0L) + { + if(bytesCreditLimit == 0L) + { + _bytesCredit = 0; + } + else + { + _bytesCredit += bytesCreditChange; + } + } + + + if(messageCreditChange != 0L) + { + if(messageCreditLimit == 0L) + { + _messageCredit = 0; + } + else + { + _messageCredit += messageCreditChange; + } + } + + + _bytesCreditLimit = bytesCreditLimit; + _messageCreditLimit = messageCreditLimit; + + setSuspended(!hasCredit()); + + } + + + public long getMessageCredit() + { + return _messageCredit; + } + + public long getBytesCredit() + { + return _bytesCredit; + } + + public synchronized void restoreCredit(final long messageCredit, final long bytesCredit) + { + final long messageCreditLimit = _messageCreditLimit; + boolean notifyIncrease = true; + if(messageCreditLimit != 0L) + { + notifyIncrease = (_messageCredit != 0); + long newCredit = _messageCredit + messageCredit; + _messageCredit = newCredit > messageCreditLimit ? messageCreditLimit : newCredit; + } + + + final long bytesCreditLimit = _bytesCreditLimit; + if(bytesCreditLimit != 0L) + { + long newCredit = _bytesCredit + bytesCredit; + _bytesCredit = newCredit > bytesCreditLimit ? bytesCreditLimit : newCredit; + if(notifyIncrease && bytesCredit>0) + { + notifyIncreaseBytesCredit(); + } + } + + + + setSuspended(!hasCredit()); + + } + + public synchronized void removeAllCredit() + { + _bytesCredit = 0L; + _messageCredit = 0L; + setSuspended(!hasCredit()); + } + + public synchronized boolean hasCredit() + { + return (_bytesCreditLimit == 0L || _bytesCredit > 0) + && (_messageCreditLimit == 0L || _messageCredit > 0); + } + + public synchronized boolean useCreditForMessage(final long msgSize) + { + if(_messageCreditLimit != 0L) + { + if(_messageCredit != 0L) + { + if(_bytesCreditLimit == 0L) + { + _messageCredit--; + + return true; + } + else + { + if((_bytesCredit >= msgSize) || (_bytesCredit == _bytesCreditLimit)) + { + _messageCredit--; + _bytesCredit -= msgSize; + + return true; + } + else + { + return false; + } + } + } + else + { + setSuspended(true); + return false; + } + } + else + { + if(_bytesCreditLimit == 0L) + { + + return true; + } + else + { + if((_bytesCredit >= msgSize) || (_bytesCredit == _bytesCreditLimit)) + { + _bytesCredit -= msgSize; + + return true; + } + else + { + return false; + } + } + + } + + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/AbstractRootMessageLogger.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/AbstractRootMessageLogger.java new file mode 100644 index 0000000000..98da9074ef --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/AbstractRootMessageLogger.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.logging; + + +public abstract class AbstractRootMessageLogger implements RootMessageLogger +{ + public static final String DEFAULT_LOG_HIERARCHY_PREFIX = "qpid.message."; + + private boolean _enabled = true; + + public AbstractRootMessageLogger() + { + + } + + public AbstractRootMessageLogger(boolean statusUpdatesEnabled) + { + _enabled = statusUpdatesEnabled; + } + + public boolean isEnabled() + { + return _enabled; + } + + public boolean isMessageEnabled(LogActor actor, LogSubject subject, String logHierarchy) + { + return _enabled; + } + + public boolean isMessageEnabled(LogActor actor, String logHierarchy) + { + return _enabled; + } + + public abstract void rawMessage(String message, String logHierarchy); + + public abstract void rawMessage(String message, Throwable throwable, String logHierarchy); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/CompositeStartupMessageLogger.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/CompositeStartupMessageLogger.java new file mode 100644 index 0000000000..e0a51b3a3e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/CompositeStartupMessageLogger.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.logging; + +public class CompositeStartupMessageLogger extends AbstractRootMessageLogger +{ + private RootMessageLogger[] _loggers; + + public CompositeStartupMessageLogger(RootMessageLogger[] loggers) + { + super(); + _loggers = loggers; + } + + @Override + public void rawMessage(String message, String logHierarchy) + { + for(RootMessageLogger l : _loggers) + { + l.rawMessage(message, logHierarchy); + } + } + + @Override + public void rawMessage(String message, Throwable throwable, String logHierarchy) + { + for(RootMessageLogger l : _loggers) + { + l.rawMessage(message, throwable, logHierarchy); + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/Log4jMessageLogger.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/Log4jMessageLogger.java new file mode 100644 index 0000000000..b4e9f2f333 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/Log4jMessageLogger.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server.logging; + +import org.apache.log4j.Logger; + +public class Log4jMessageLogger extends AbstractRootMessageLogger +{ + public Log4jMessageLogger() + { + super(); + } + + public Log4jMessageLogger(boolean statusUpdatesEnabled) + { + super(statusUpdatesEnabled); + } + + @Override + public boolean isMessageEnabled(LogActor actor, LogSubject subject, String logHierarchy) + { + return isMessageEnabled(actor, logHierarchy); + } + + @Override + public boolean isMessageEnabled(LogActor actor, String logHierarchy) + { + if(isEnabled()) + { + Logger logger = Logger.getLogger(logHierarchy); + return logger.isInfoEnabled(); + } + else + { + return false; + } + } + + @Override + public void rawMessage(String message, String logHierarchy) + { + rawMessage(message, null, logHierarchy); + } + + @Override + public void rawMessage(String message, Throwable throwable, String logHierarchy) + { + Logger logger = Logger.getLogger(logHierarchy); + logger.info(message, throwable); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogActor.java new file mode 100644 index 0000000000..18f03c2716 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogActor.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.logging; + +/** + * LogActor the entity that is stored as in a ThreadLocal and used to perform logging. + * + * The actor is responsible for formatting its display name for the log entry. + * + * The actor performs the requested logging. + */ +public interface LogActor +{ + /** + * Logs the specified LogMessage about the LogSubject + * + * Currently logging has a global setting however this will later be revised and + * as such the LogActor will need to take into consideration any new configuration + * as a means of enabling the logging of LogActors and LogSubjects. + * + * @param subject The subject that is being logged + * @param message The message to log + */ + public void message(LogSubject subject, LogMessage message); + + /** + * Logs the specified LogMessage against this actor + * + * Currently logging has a global setting however this will later be revised and + * as such the LogActor will need to take into consideration any new configuration + * as a means of enabling the logging of LogActors and LogSubjects. + * + * @param message The message to log + */ + public void message(LogMessage message); + + /** + * + * @return the RootMessageLogger that is currently in use by this LogActor. + */ + RootMessageLogger getRootMessageLogger(); + + /** + * + * @return the String representing this LogActor + */ + public String getLogMessage(); +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogMessage.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogMessage.java new file mode 100644 index 0000000000..fa18435fab --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogMessage.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.logging; + +public interface LogMessage +{ + String getLogHierarchy(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogRecorder.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogRecorder.java new file mode 100644 index 0000000000..dfffbdbb5f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogRecorder.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.qpid.server.logging; + +import java.util.Iterator; +import org.apache.log4j.Appender; +import org.apache.log4j.Layout; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.ErrorHandler; +import org.apache.log4j.spi.Filter; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.ThrowableInformation; +import org.apache.qpid.server.configuration.BrokerProperties; + +public class LogRecorder implements Appender, Iterable +{ + private static final int DEFAULT_BUFFER_SIZE = 4096; + private ErrorHandler _errorHandler; + private Filter _filter; + private String _name; + private long _recordId; + + private final int _bufferSize = Integer.getInteger(BrokerProperties.PROPERTY_LOG_RECORDS_BUFFER_SIZE, DEFAULT_BUFFER_SIZE); + private final int _mask = _bufferSize - 1; + private Record[] _records = new Record[_bufferSize]; + + + public static class Record + { + private final long _id; + private final String _logger; + private final long _timestamp; + private final String _threadName; + private final String _level; + private final String _message; + + + public Record(long id, LoggingEvent event) + { + _id = id; + _logger = event.getLoggerName(); + _timestamp = event.timeStamp; + _threadName = event.getThreadName(); + _level = event.getLevel().toString(); + StringBuilder message = new StringBuilder(); + String renderedMessage = event.getRenderedMessage(); + if (renderedMessage != null) + { + message.append(renderedMessage); + } + ThrowableInformation ti = event.getThrowableInformation(); + if (ti != null) + { + Throwable t = ti.getThrowable(); + if (t != null) + { + if (message.length() > 0) + { + message.append(":"); + } + String exceptionMessage = t.getMessage(); + if (exceptionMessage != null && !"".equals(exceptionMessage)) + { + message.append(t.getMessage()); + } + else + { + message.append(t.getClass().getName()); + } + } + } + _message = message.toString(); + } + + public long getId() + { + return _id; + } + + public long getTimestamp() + { + return _timestamp; + } + + public String getThreadName() + { + return _threadName; + } + + public String getLevel() + { + return _level; + } + + public String getMessage() + { + return _message; + } + + public String getLogger() + { + return _logger; + } + } + + public LogRecorder() + { + Logger.getRootLogger().addAppender(this); + } + + @Override + public void addFilter(Filter filter) + { + _filter = filter; + } + + @Override + public void clearFilters() + { + _filter = null; + } + + @Override + public void close() + { + } + + public void closeLogRecorder() + { + Logger.getRootLogger().removeAppender(this); + } + + @Override + public synchronized void doAppend(LoggingEvent loggingEvent) + { + _records[((int) (_recordId & _mask))] = new Record(_recordId, loggingEvent); + _recordId++; + } + + @Override + public ErrorHandler getErrorHandler() + { + return _errorHandler; + } + + @Override + public Filter getFilter() + { + return _filter; + } + + @Override + public Layout getLayout() + { + return null; + } + + @Override + public String getName() + { + return _name; + } + + @Override + public boolean requiresLayout() + { + return false; + } + + @Override + public void setErrorHandler(ErrorHandler errorHandler) + { + _errorHandler = errorHandler; + } + + @Override + public void setLayout(Layout layout) + { + + } + + @Override + public void setName(String name) + { + _name = name; + } + + @Override + public Iterator iterator() + { + return new RecordIterator(Math.max(_recordId-_bufferSize, 0l)); + } + + private class RecordIterator implements Iterator + { + private long _id; + + public RecordIterator(long currentRecordId) + { + _id = currentRecordId; + } + + @Override + public boolean hasNext() + { + return _id < _recordId; + } + + @Override + public Record next() + { + Record record = _records[((int) (_id & _mask))]; + while(_id < _recordId-_bufferSize) + { + _id = _recordId-_bufferSize; + record = _records[((int) (_id & _mask))]; + } + _id++; + return record; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogSubject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogSubject.java new file mode 100644 index 0000000000..09a277e520 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/LogSubject.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.logging; + +/** + * Each LogSubject that wishes to be logged will implement this to provide their + * own display representation. + * + */ +public interface LogSubject +{ + /** + * Provides the log message as as String. + * + * @returns String the display representation of this LogSubject + */ + public String toLogString(); +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/NullRootMessageLogger.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/NullRootMessageLogger.java new file mode 100644 index 0000000000..db8b24e90e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/NullRootMessageLogger.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.logging; + +public class NullRootMessageLogger extends AbstractRootMessageLogger +{ + + @Override + public boolean isMessageEnabled(LogActor actor, LogSubject subject, String logHeirarchy) + { + return false; + } + + @Override + public boolean isMessageEnabled(LogActor actor, String logHierarchy) + { + return false; + } + + public void rawMessage(String message, String logHierarchy) + { + // drop message + } + + public void rawMessage(String message, Throwable throwable, String logHierarchy) + { + // drop message + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/RootMessageLogger.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/RootMessageLogger.java new file mode 100644 index 0000000000..1431dd1da9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/RootMessageLogger.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.logging; + +/** + * The RootMessageLogger is used by the LogActors to query if + * logging is enabled for the requested message and to provide the actual + * message that should be logged. + */ +public interface RootMessageLogger +{ + /** + * Determine whether the MessageLogger is enabled + * + * @return boolean true if enabled. + */ + boolean isEnabled(); + + /** + * Determine if the LogSubject and the LogActor should be + * generating log messages. + * @param actor The actor requesting the logging + * @param subject The subject of this log request + * @param logHierarchy The log hierarchy for this request + * + * @return boolean true if the message should be logged. + */ + boolean isMessageEnabled(LogActor actor, LogSubject subject, String logHierarchy); + + /** + * Determine if the LogActor should be generating log messages. + * + * @param actor The actor requesting the logging + * @param logHierarchy The log hierarchy for this request + * + * @return boolean true if the message should be logged. + */ + boolean isMessageEnabled(LogActor actor, String logHierarchy); + + /** + * Log the raw message to the configured logger. + * + * @param message The message to log + * @param logHierarchy The log hierarchy for this request + */ + public void rawMessage(String message, String logHierarchy); + + /** + * Log the raw message to the configured logger. + * Along with a formated stack trace from the Throwable. + * + * @param message The message to log + * @param throwable Optional Throwable that should provide stact trace + * @param logHierarchy The log hierarchy for this request + */ + void rawMessage(String message, Throwable throwable, String logHierarchy); +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/SystemOutMessageLogger.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/SystemOutMessageLogger.java new file mode 100644 index 0000000000..b384b3fde3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/SystemOutMessageLogger.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.logging; + + +public class SystemOutMessageLogger extends AbstractRootMessageLogger +{ + @Override + public boolean isMessageEnabled(LogActor actor, LogSubject subject, String logHeirarchy) + { + return true; + } + + @Override + public boolean isMessageEnabled(LogActor actor, String logHierarchy) + { + return true; + } + + public void rawMessage(String message, String logHierarchy) + { + rawMessage(message, null, logHierarchy); + } + + public void rawMessage(String message, Throwable throwable, String logHierarchy) + { + System.out.println(message); + if (throwable != null) + { + throwable.printStackTrace(System.out); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AMQPChannelActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AMQPChannelActor.java new file mode 100644 index 0000000000..d23122ac5d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AMQPChannelActor.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.logging.actors; + +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.ChannelLogSubject; + +/** + * An AMQPChannelActor represtents a connection through the AMQP port with an + * associated Channel. + * + *

+ * This is responsible for correctly formatting the LogActor String in the log + *

+ * [con:1(user@127.0.0.1/)/ch:1] + *

+ * To do this it requires access to the IO Layers as well as a Channel + */ +public class AMQPChannelActor extends AbstractActor +{ + private final ChannelLogSubject _logString; + + /** + * Create a new ChannelActor + * + * @param channel The Channel for this LogActor + * @param rootLogger The root Logger that this LogActor should use + */ + public AMQPChannelActor(AMQSessionModel channel, RootMessageLogger rootLogger) + { + super(rootLogger); + + + _logString = new ChannelLogSubject(channel); + } + + public String getLogMessage() + { + return _logString.toLogString(); + } +} + diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AMQPConnectionActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AMQPConnectionActor.java new file mode 100644 index 0000000000..99e11bf9ae --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AMQPConnectionActor.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.logging.actors; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.ConnectionLogSubject; +import org.apache.qpid.server.protocol.AMQConnectionModel; + + +/** + * An AMQPConnectionActor represtents a connectionthrough the AMQP port. + *

+ * This is responsible for correctly formatting the LogActor String in the log + *

+ * [ con:1(user@127.0.0.1/) ] + *

+ * To do this it requires access to the IO Layers. + */ +public class AMQPConnectionActor extends AbstractActor +{ + private ConnectionLogSubject _logSubject; + + public AMQPConnectionActor(AMQConnectionModel session, RootMessageLogger rootLogger) + { + super(rootLogger); + + _logSubject = new ConnectionLogSubject(session); + } + + public String getLogMessage() + { + return _logSubject.toLogString(); + } +} + diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AbstractActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AbstractActor.java new file mode 100644 index 0000000000..e8c6c9c323 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AbstractActor.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.logging.actors; + +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.RootMessageLogger; + +public abstract class AbstractActor implements LogActor +{ + private final String _msgPrefix = System.getProperty("qpid.logging.prefix",""); + + private RootMessageLogger _rootLogger; + + public AbstractActor(RootMessageLogger rootLogger) + { + if(rootLogger == null) + { + throw new NullPointerException("RootMessageLogger cannot be null"); + } + _rootLogger = rootLogger; + } + + public void message(LogSubject subject, LogMessage message) + { + if (_rootLogger.isMessageEnabled(this, subject, message.getLogHierarchy())) + { + _rootLogger.rawMessage(_msgPrefix + getLogMessage() + subject.toLogString() + message, message.getLogHierarchy()); + } + } + + public void message(LogMessage message) + { + if (_rootLogger.isMessageEnabled(this, message.getLogHierarchy())) + { + _rootLogger.rawMessage(_msgPrefix + getLogMessage() + message, message.getLogHierarchy()); + } + } + + public RootMessageLogger getRootMessageLogger() + { + return _rootLogger; + } + + public String toString() + { + return getLogMessage(); + } + + abstract public String getLogMessage(); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AbstractManagementActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AbstractManagementActor.java new file mode 100644 index 0000000000..8cf121b3d9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/AbstractManagementActor.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.logging.actors; + +import java.security.AccessController; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; + +public abstract class AbstractManagementActor extends AbstractActor +{ + /** + * Holds the principal name to display when principal subject is not available. + *

+ * This is useful for cases when users invoke JMX operation over JConsole + * attached to the local JVM. + */ + protected static final String UNKNOWN_PRINCIPAL = "N/A"; + + /** used when the principal name cannot be discovered from the Subject */ + private final String _fallbackPrincipalName; + + public AbstractManagementActor(RootMessageLogger rootLogger, String fallbackPrincipalName) + { + super(rootLogger); + _fallbackPrincipalName = fallbackPrincipalName; + } + + /** + * Returns current {@link AuthenticatedPrincipal} name or {@link #_fallbackPrincipalName} + * if it can't be found. + */ + protected String getPrincipalName() + { + String identity = _fallbackPrincipalName; + + final Subject subject = Subject.getSubject(AccessController.getContext()); + if (subject != null) + { + AuthenticatedPrincipal authenticatedPrincipal = AuthenticatedPrincipal.getOptionalAuthenticatedPrincipalFromSubject(subject); + if(authenticatedPrincipal != null) + { + identity = authenticatedPrincipal.getName(); + } + } + return identity; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/BrokerActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/BrokerActor.java new file mode 100644 index 0000000000..9e77452228 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/BrokerActor.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.logging.actors; + +import org.apache.qpid.server.logging.RootMessageLogger; + +public class BrokerActor extends AbstractActor +{ + private final String _logString; + + /** + * Create a new BrokerActor + * + * @param logger + */ + public BrokerActor(RootMessageLogger logger) + { + super(logger); + + _logString = "[Broker] "; + } + + public BrokerActor(String name, RootMessageLogger logger) + { + super(logger); + + _logString = "[Broker(" + name + ")] "; + } + + public String getLogMessage() + { + return _logString; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/CurrentActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/CurrentActor.java new file mode 100644 index 0000000000..6251471139 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/CurrentActor.java @@ -0,0 +1,146 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.actors; + +import java.util.EmptyStackException; +import java.util.Stack; + +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; + +/** + * The CurrentActor is a ThreadLocal wrapper that allows threads in the broker + * to retrieve an actor to perform logging. This approach is used so for two + * reasons: + * 1) We do not have to pass a logging actor around the system + * 2) We can set new actors at the point we have enough information. i.e. + * - Set a low level ConnectionActor when processing bytes from the wire. + * - Set a ChannelActor when we are processing the frame + * - Set a SubscriptionActor when we are handling the subscription. + *

+ * The code performing the logging need not worry about what type of actor is + * currently set so can perform its logging. The resulting log entry though will + * contain customised details from the the currently set Actor. + *

+ * The Actor model also allows the pre-creation of fixed messages so the + * performance impact of the additional logging data is minimised. + *

+ * This class does not perform any checks to ensure that there is an Actor set + * when calling remove or get. As a result the application developer must ensure + * that they have called set before they attempt to use the actor via get or + * remove the set actor. + *

+ * The checking of the return via get should not be done as the logging is + * desired. It is preferable to cause the NullPointerException to highlight the + * programming error rather than miss a log message. + *

+ * The same is true for the remove. A NPE will occur if no set has been called + * highlighting the programming error. + */ +public class CurrentActor +{ + /** The ThreadLocal variable with initialiser */ + private static final ThreadLocal> _currentActor = new ThreadLocal>() + { + // Initialise the CurrentActor to be an empty List + protected Stack initialValue() + { + return new Stack(); + } + }; + + private static LogActor _defaultActor; + + private CurrentActor() + { + } + + /** + * Set a new {@link LogActor} to be the Current Actor + *

+ * This pushes the Actor in to the LIFO Queue + * + * @param actor The new LogActor + */ + public static void set(LogActor actor) + { + Stack stack = _currentActor.get(); + stack.push(actor); + } + + /** + * Remove all {@link LogActor}s + */ + public static void removeAll() + { + Stack stack = _currentActor.get(); + stack.clear(); + } + + /** + * Remove the current {@link LogActor}. + *

+ * Calling remove without calling set will result in an EmptyStackException. + */ + public static void remove() + { + Stack stack = _currentActor.get(); + stack.pop(); + + if (stack.isEmpty()) + { + _currentActor.remove(); + } + } + + /** + * Return the current head of the list of {@link LogActor}s. + * + * @return Current LogActor + */ + public static LogActor get() + { + try + { + return _currentActor.get().peek(); + } + catch (EmptyStackException ese) + { + return _defaultActor; + } + } + + public static void setDefault(LogActor defaultActor) + { + _defaultActor = defaultActor; + } + + public static void message(LogSubject subject, LogMessage message) + { + get().message(subject, message); + } + + public static void message(LogMessage message) + { + get().message(message); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/GenericActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/GenericActor.java new file mode 100644 index 0000000000..0e418a95e2 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/GenericActor.java @@ -0,0 +1,84 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.logging.actors; + +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.RootMessageLogger; + +public class GenericActor extends AbstractActor +{ + + private static RootMessageLogger _defaultMessageLogger; + + private LogSubject _logSubject; + + public static RootMessageLogger getDefaultMessageLogger() + { + return _defaultMessageLogger; + } + + public static void setDefaultMessageLogger(RootMessageLogger defaultMessageLogger) + { + _defaultMessageLogger = defaultMessageLogger; + } + + public GenericActor(LogSubject logSubject, RootMessageLogger rootLogger) + { + super(rootLogger); + _logSubject = logSubject; + } + + public String getLogMessage() + { + return _logSubject.toLogString(); + } + + public static LogActor getInstance(final String logMessage, RootMessageLogger rootLogger) + { + return new GenericActor(new LogSubject() + { + public String toLogString() + { + return logMessage; + } + + }, rootLogger); + } + + public static LogActor getInstance(final String subjectMessage) + { + return new GenericActor(new LogSubject() + { + public String toLogString() + { + return "[" + subjectMessage + "] "; + } + + }, _defaultMessageLogger); + } + + public static LogActor getInstance(LogSubject logSubject) + { + return new GenericActor(logSubject, _defaultMessageLogger); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/HttpManagementActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/HttpManagementActor.java new file mode 100644 index 0000000000..9b445c2bd9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/HttpManagementActor.java @@ -0,0 +1,62 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.actors; + +import java.text.MessageFormat; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.LogSubjectFormat; + +/** + * HttpManagement actor to use in {@link AbstractServlet} to log all http management operational logging. + * + * An instance is required per http Session. + */ +public class HttpManagementActor extends AbstractManagementActor +{ + private String _cachedLogString; + private String _lastPrincipalName; + private String _address; + + public HttpManagementActor(RootMessageLogger rootLogger, String ip, int port) + { + super(rootLogger, UNKNOWN_PRINCIPAL); + _address = ip + ":" + port; + } + + private synchronized String getAndCacheLogString() + { + String principalName = getPrincipalName(); + + if(!principalName.equals(_lastPrincipalName)) + { + _lastPrincipalName = principalName; + _cachedLogString = "[" + MessageFormat.format(LogSubjectFormat.MANAGEMENT_FORMAT, principalName, _address) + "] "; + } + + return _cachedLogString; + } + + public String getLogMessage() + { + return getAndCacheLogString(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.java new file mode 100644 index 0000000000..ba5ea47fc1 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.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.logging.actors; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.LogSubjectFormat; + +import java.text.MessageFormat; + +/** + * Management actor to use in {@link MBeanInvocationHandlerImpl} to log all management operational logging. + */ +public class ManagementActor extends AbstractManagementActor +{ + private String _lastThreadName = null; + + /** + * The logString to be used for logging + */ + private String _logStringContainingPrincipal; + + /** @param rootLogger The RootLogger to use for this Actor */ + public ManagementActor(RootMessageLogger rootLogger) + { + super(rootLogger, UNKNOWN_PRINCIPAL); + } + + public ManagementActor(RootMessageLogger rootLogger, String principalName) + { + super(rootLogger, principalName); + } + + private synchronized String getAndCacheLogString() + { + String currentName = Thread.currentThread().getName(); + + String actor; + String logString = _logStringContainingPrincipal; + + // Record the last thread name so we don't have to recreate the log string + if (_logStringContainingPrincipal == null || !currentName.equals(_lastThreadName)) + { + _lastThreadName = currentName; + String principalName = getPrincipalName(); + + // Management Thread names have this format. + // RMI TCP Connection(2)-169.24.29.116 + // This is true for both LocalAPI and JMX Connections + // However to be defensive lets test. + String[] split = currentName.split("\\("); + if (split.length == 2) + { + String ip = currentName.split("-")[1]; + actor = MessageFormat.format(LogSubjectFormat.MANAGEMENT_FORMAT, principalName, ip); + } + else + { + // This is a precautionary path as it is not expected to occur + // however rather than adjusting the thread name of the two + // tests that will use this it is safer all round to do this. + // it is also currently used by tests : + // AMQBrokerManagerMBeanTest + // ExchangeMBeanTest + actor = currentName; + } + + logString = "[" + actor + "] "; + if(principalName != UNKNOWN_PRINCIPAL ) + { + _logStringContainingPrincipal = logString; + } + + } + return logString; + } + + public String getLogMessage() + { + return getAndCacheLogString(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/QueueActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/QueueActor.java new file mode 100644 index 0000000000..4b17e8c0e6 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/QueueActor.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.logging.actors; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.QueueLogSubject; +import org.apache.qpid.server.queue.AMQQueue; + +/** + * This Actor is used when while the queue is performing an asynchronous process + * of its queue. + */ +public class QueueActor extends AbstractActor +{ + private QueueLogSubject _logSubject; + + /** + * Create an QueueLogSubject that Logs in the following format. + * + * @param queue The queue that this Actor is working for + * @param rootLogger the Root logger to use. + */ + public QueueActor(AMQQueue queue, RootMessageLogger rootLogger) + { + super(rootLogger); + + _logSubject = new QueueLogSubject(queue); + } + + public String getLogMessage() + { + return _logSubject.toLogString(); + } +} + diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/SubscriptionActor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/SubscriptionActor.java new file mode 100644 index 0000000000..a2dbf2f6ee --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/actors/SubscriptionActor.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.logging.actors; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.SubscriptionLogSubject; +import org.apache.qpid.server.subscription.Subscription; + +/** + * The subscription actor provides formatted logging for actions that are + * performed by the subsciption. Such as STATE changes. + */ +public class SubscriptionActor extends AbstractActor +{ + private SubscriptionLogSubject _logSubject; + + public SubscriptionActor(RootMessageLogger logger, Subscription subscription) + { + super(logger); + + _logSubject = new SubscriptionLogSubject(subscription); + } + + public String getLogMessage() + { + return _logSubject.toLogString(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/log4j/LoggingFacadeException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/log4j/LoggingFacadeException.java new file mode 100644 index 0000000000..468b06be34 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/log4j/LoggingFacadeException.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.logging.log4j; + +public class LoggingFacadeException extends Exception +{ + + public LoggingFacadeException() + { + super(); + } + + public LoggingFacadeException(String message, Throwable cause) + { + super(message, cause); + } + + public LoggingFacadeException(String message) + { + super(message); + } + + public LoggingFacadeException(Throwable cause) + { + super(cause); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/log4j/LoggingManagementFacade.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/log4j/LoggingManagementFacade.java new file mode 100644 index 0000000000..6a961c8fa4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/log4j/LoggingManagementFacade.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.logging.log4j; + +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.xml.DOMConfigurator; +import org.apache.log4j.xml.Log4jEntityResolver; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +/** + * A facade over log4j that allows both the control of the runtime logging behaviour (that is, the ability to + * turn {@link Logger} on, off and control their {@link Level}, and the manipulation and reload + * of the log4j configuration file. + */ +public class LoggingManagementFacade +{ + private static Logger LOGGER; + private static transient LoggingManagementFacade _instance; + private final String _filename; + private final int _delay; + + public static LoggingManagementFacade configure(String filename) throws LoggingFacadeException + { + _instance = new LoggingManagementFacade(filename); + return _instance; + } + + public static LoggingManagementFacade configureAndWatch(String filename, int delay) throws LoggingFacadeException + { + _instance = new LoggingManagementFacade(filename, delay); + return _instance; + } + + public static LoggingManagementFacade getCurrentInstance() + { + return _instance; + } + + private LoggingManagementFacade(String filename) + { + DOMConfigurator.configure(filename); + + if(LOGGER == null) + { + LOGGER = Logger.getLogger(LoggingManagementFacade.class); + } + _filename = filename; + _delay = 0; + } + + private LoggingManagementFacade(String filename, int delay) + { + DOMConfigurator.configureAndWatch(filename, delay); + + if(LOGGER == null) + { + LOGGER = Logger.getLogger(LoggingManagementFacade.class); + } + + _filename = filename; + _delay = delay; + } + + public int getLog4jLogWatchInterval() + { + return _delay; + } + + public synchronized void reload() throws LoggingFacadeException + { + DOMConfigurator.configure(_filename); + } + + /** The log4j XML configuration file DTD defines three possible element + * combinations for specifying optional logger+level settings. + * Must account for the following: + * + * OR + * OR + * + * + * Noting also that the level/priority child element is optional too, + * and not the only possible child element. + */ + public synchronized Map retrieveConfigFileLoggersLevels() throws LoggingFacadeException + { + try + { + Map loggerLevelList = new HashMap(); + LOGGER.info("Getting logger levels from log4j configuration file"); + + Document doc = parseConfigFile(_filename); + List categoryOrLoggerElements = buildListOfCategoryOrLoggerElements(doc); + + for (Element categoryOrLogger : categoryOrLoggerElements) + { + + Element priorityOrLevelElement; + try + { + priorityOrLevelElement = getPriorityOrLevelElement(categoryOrLogger); + } + catch (LoggingFacadeException lfe) + { + //there is no exiting priority or level to view, move onto next category/logger + continue; + } + + String categoryName = categoryOrLogger.getAttribute("name"); + String priorityOrLevelValue = priorityOrLevelElement.getAttribute("value"); + loggerLevelList.put(categoryName, priorityOrLevelValue); + } + + return loggerLevelList; + } + catch (IOException e) + { + throw new LoggingFacadeException(e); + } + } + + /** + * The log4j XML configuration file DTD defines 2 possible element + * combinations for specifying the optional root logger level settings + * Must account for the following: + * + * OR + * + * + * Noting also that the level/priority child element is optional too, + * and not the only possible child element. + */ + public synchronized String retrieveConfigFileRootLoggerLevel() throws LoggingFacadeException + { + try + { + Document doc = parseConfigFile(_filename); + + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); + + if (rootElements.getLength() == 0) + { + //there is no root logger definition + return "N/A"; + } + + Element rootElement = (Element) rootElements.item(0); + Element levelElement = getPriorityOrLevelElement(rootElement); + + if(levelElement != null) + { + return levelElement.getAttribute("value"); + } + else + { + return "N/A"; + } + } + catch (IOException e) + { + throw new LoggingFacadeException(e); + } + } + + public synchronized void setConfigFileLoggerLevel(String logger, String level) throws LoggingFacadeException + { + LOGGER.info("Setting level to " + level + " for logger '" + logger + + "' in log4j xml configuration file: " + _filename); + + try + { + Document doc = parseConfigFile(_filename); + + List logElements = buildListOfCategoryOrLoggerElements(doc); + + //try to locate the specified logger/category in the elements retrieved + Element logElement = null; + for (Element e : logElements) + { + if (e.getAttribute("name").equals(logger)) + { + logElement = e; + break; + } + } + + if (logElement == null) + { + throw new LoggingFacadeException("Can't find logger " + logger); + } + + Element levelElement = getPriorityOrLevelElement(logElement); + + //update the element with the new level/priority + levelElement.setAttribute("value", level); + + //output the new file + writeUpdatedConfigFile(_filename, doc); + } + catch (IOException ioe) + { + throw new LoggingFacadeException(ioe); + } + catch (TransformerConfigurationException e) + { + throw new LoggingFacadeException(e); + } + } + + public synchronized void setConfigFileRootLoggerLevel(String level) throws LoggingFacadeException + { + try + { + LOGGER.info("Setting level to " + level + " for the Root logger in " + + "log4j xml configuration file: " + _filename); + + Document doc = parseConfigFile(_filename); + + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); + + if (rootElements.getLength() == 0) + { + throw new LoggingFacadeException("Configuration contains no root element"); + } + + Element rootElement = (Element) rootElements.item(0); + Element levelElement = getPriorityOrLevelElement(rootElement); + + //update the element with the new level/priority + levelElement.setAttribute("value", level); + + //output the new file + writeUpdatedConfigFile(_filename, doc); + } + catch (IOException e) + { + throw new LoggingFacadeException(e); + } + catch (TransformerConfigurationException e) + { + throw new LoggingFacadeException(e); + } + } + + public List getAvailableLoggerLevels() + { + return new ArrayList() + {{ + add(Level.ALL.toString()); + add(Level.TRACE.toString()); + add(Level.DEBUG.toString()); + add(Level.INFO.toString()); + add(Level.WARN.toString()); + add(Level.ERROR.toString()); + add(Level.FATAL.toString()); + add(Level.OFF.toString()); + }}; + } + + public String retrieveRuntimeRootLoggerLevel() + { + Logger rootLogger = Logger.getRootLogger(); + return rootLogger.getLevel().toString(); + } + + public void setRuntimeRootLoggerLevel(String level) + { + Level newLevel = Level.toLevel(level); + + LOGGER.info("Setting RootLogger level to " + level); + + Logger log = Logger.getRootLogger(); + log.setLevel(newLevel); + } + + public void setRuntimeLoggerLevel(String loggerName, String level) throws LoggingFacadeException + { + Level newLevel = level == null ? null : Level.toLevel(level); + + Logger targetLogger = findRuntimeLogger(loggerName); + + if(targetLogger == null) + { + throw new LoggingFacadeException("Can't find logger " + loggerName); + } + + LOGGER.info("Setting level to " + newLevel + " for logger '" + targetLogger.getName() + "'"); + + targetLogger.setLevel(newLevel); + } + + public Map retrieveRuntimeLoggersLevels() + { + LOGGER.info("Getting levels for currently active log4j loggers"); + + Map levels = new HashMap(); + @SuppressWarnings("unchecked") + Enumeration loggers = LogManager.getCurrentLoggers(); + + while (loggers.hasMoreElements()) + { + Logger logger = loggers.nextElement(); + levels.put(logger.getName(), logger.getEffectiveLevel().toString()); + } + + return levels; + } + + private void writeUpdatedConfigFile(String log4jConfigFileName, Document doc) throws IOException, TransformerConfigurationException + { + File log4jConfigFile = new File(log4jConfigFileName); + + if (!log4jConfigFile.canWrite()) + { + LOGGER.warn("Specified log4j XML configuration file is not writable: " + log4jConfigFile); + throw new IOException("Specified log4j XML configuration file is not writable"); + } + + Transformer transformer = null; + transformer = TransformerFactory.newInstance().newTransformer(); + + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "log4j.dtd"); + DOMSource source = new DOMSource(doc); + + File tmp; + Random r = new Random(); + + do + { + tmp = new File(log4jConfigFile.getAbsolutePath() + r.nextInt() + ".tmp"); + } + while(tmp.exists()); + + tmp.deleteOnExit(); + + try + { + StreamResult result = new StreamResult(new FileOutputStream(tmp)); + transformer.transform(source, result); + } + catch (TransformerException e) + { + LOGGER.warn("Could not transform the XML into new file: ", e); + throw new IOException("Could not transform the XML into new file: ", e); + } + + // Swap temp file in to replace existing configuration file. + File old = new File(log4jConfigFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + if(!log4jConfigFile.renameTo(old)) + { + //unable to rename the existing file to the backup name + LOGGER.error("Could not backup the existing log4j XML file"); + throw new IOException("Could not backup the existing log4j XML file"); + } + + if(!tmp.renameTo(log4jConfigFile)) + { + //failed to rename the new file to the required filename + + if(!old.renameTo(log4jConfigFile)) + { + //unable to return the backup to required filename + LOGGER.error("Could not rename the new log4j configuration file into place, and unable to restore original file"); + throw new IOException("Could not rename the new log4j configuration file into place, and unable to restore original file"); + } + + LOGGER.error("Could not rename the new log4j configuration file into place"); + throw new IOException("Could not rename the new log4j configuration file into place"); + } + } + + //method to parse the XML configuration file, validating it in the process, and returning a DOM Document of the content. + private static Document parseConfigFile(String fileName) throws IOException + { + //check file was specified, exists, and is readable + if(fileName == null) + { + LOGGER.warn("Provided log4j XML configuration filename is null"); + throw new IOException("Provided log4j XML configuration filename is null"); + } + + File configFile = new File(fileName); + + if (!configFile.exists()) + { + LOGGER.warn("The log4j XML configuration file could not be found: " + fileName); + throw new IOException("The log4j XML configuration file could not be found"); + } + else if (!configFile.canRead()) + { + LOGGER.warn("The log4j XML configuration file is not readable: " + fileName); + throw new IOException("The log4j XML configuration file is not readable"); + } + + //parse it + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + Document doc; + + ErrorHandler errHandler = new QpidLog4JSaxErrorHandler(); + try + { + docFactory.setValidating(true); + docBuilder = docFactory.newDocumentBuilder(); + docBuilder.setErrorHandler(errHandler); + docBuilder.setEntityResolver(new Log4jEntityResolver()); + doc = docBuilder.parse(fileName); + } + catch (ParserConfigurationException e) + { + LOGGER.warn("Unable to parse the log4j XML file due to possible configuration error: ", e); + throw new IOException("Unable to parse the log4j XML file due to possible configuration error: ", e); + } + catch (SAXException e) + { + LOGGER.warn("The specified log4j XML file is invalid: ", e); + throw new IOException("The specified log4j XML file is invalid: ", e); + } + catch (IOException e) + { + LOGGER.warn("Unable to parse the specified log4j XML file", e); + throw new IOException("Unable to parse the specified log4j XML file: ", e); + } + + return doc; + } + + private Logger findRuntimeLogger(String loggerName) + { + Logger targetLogger = null; + @SuppressWarnings("unchecked") + Enumeration loggers = LogManager.getCurrentLoggers(); + while(loggers.hasMoreElements()) + { + targetLogger = loggers.nextElement(); + if (targetLogger.getName().equals(loggerName)) + { + return targetLogger; + } + } + return null; + } + + private List buildListOfCategoryOrLoggerElements(Document doc) + { + //retrieve the 'category' and 'logger' element nodes + NodeList categoryElements = doc.getElementsByTagName("category"); + NodeList loggerElements = doc.getElementsByTagName("logger"); + + //collect them into a single elements list + List logElements = new ArrayList(); + + for (int i = 0; i < categoryElements.getLength(); i++) + { + logElements.add((Element) categoryElements.item(i)); + } + for (int i = 0; i < loggerElements.getLength(); i++) + { + logElements.add((Element) loggerElements.item(i)); + } + return logElements; + } + + private Element getPriorityOrLevelElement(Element categoryOrLogger) throws LoggingFacadeException + { + //retrieve the optional 'priority' or 'level' sub-element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = categoryOrLogger.getElementsByTagName("priority"); + NodeList levelElements = categoryOrLogger.getElementsByTagName("level"); + + Element levelElement = null; + if (priorityElements.getLength() != 0) + { + levelElement = (Element) priorityElements.item(0); + } + else if (levelElements.getLength() != 0) + { + levelElement = (Element) levelElements.item(0); + } + else + { + throw new LoggingFacadeException("Configuration " + categoryOrLogger.getNodeName() + + " element contains neither priority nor level child"); + } + return levelElement; + } + + private static class QpidLog4JSaxErrorHandler implements ErrorHandler + { + public void error(SAXParseException e) throws SAXException + { + if(LOGGER != null) + { + LOGGER.warn(constructMessage("Error parsing XML file", e)); + } + else + { + System.err.println(constructMessage("Error parsing XML file", e)); + } + } + + public void fatalError(SAXParseException e) throws SAXException + { + throw new SAXException(constructMessage("Fatal error parsing XML file", e)); + } + + public void warning(SAXParseException e) throws SAXException + { + if(LOGGER != null) + { + LOGGER.warn(constructMessage("Warning parsing XML file", e)); + } + else + { + System.err.println(constructMessage("Warning parsing XML file", e)); + } + } + + private static String constructMessage(final String msg, final SAXParseException ex) + { + return msg + ": Line " + ex.getLineNumber()+" column " +ex.getColumnNumber() + ": " + ex.getMessage(); + } + } +} + diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Binding_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Binding_logmessages.properties new file mode 100644 index 0000000000..808ec7918f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Binding_logmessages.properties @@ -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. +# +# Default File used for all non-defined locales. +# +CREATED = BND-1001 : Create[ : Arguments : {0}] +DELETED = BND-1002 : Deleted diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Broker_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Broker_logmessages.properties new file mode 100644 index 0000000000..76c1fa1b5b --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Broker_logmessages.properties @@ -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. +# +# Default File used for all non-defined locales. + +# 0 - Version +# 1 = Build +STARTUP = BRK-1001 : Startup : Version: {0} Build: {1} +# 0 - Transport +# 1 - Port +LISTENING = BRK-1002 : Starting : Listening on {0} port {1,number,#} +# 0 - Transport +# 1 - Port +SHUTTING_DOWN = BRK-1003 : Shutting down : {0} port {1,number,#} +READY = BRK-1004 : Qpid Broker Ready +STOPPED = BRK-1005 : Stopped +# 0 - path +CONFIG = BRK-1006 : Using configuration : {0} +# 0 - path +LOG_CONFIG = BRK-1007 : Using logging configuration : {0} + +STATS_DATA = BRK-1008 : {0,choice,0#delivered|1#received} : {1,number,#.###} kB/s peak : {2,number,#} bytes total +STATS_MSGS = BRK-1009 : {0,choice,0#delivered|1#received} : {1,number,#.###} msg/s peak : {2,number,#} msgs total + +# 0 - java vendor +# 1 - java runtime version +# 2 - os name +# 3 - os type +# 4 - os architecture +PLATFORM = BRK-1010 : Platform : JVM : {0} version: {1} OS : {2} version: {3} arch: {4} + +# 0 Maximum Memory +MAX_MEMORY = BRK-1011 : Maximum Memory : {0,number} bytes + +MANAGEMENT_MODE = BRK-1012 : Management Mode : User Details : {0} / {1} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Channel_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Channel_logmessages.properties new file mode 100644 index 0000000000..397c12d73c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Channel_logmessages.properties @@ -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. +# +# Default File used for all non-defined locales. + +CREATE = CHN-1001 : Create +# 0 - flow +FLOW = CHN-1002 : Flow {0} +CLOSE = CHN-1003 : Close +CLOSE_FORCED = CHN-1003 : Close : {0,number} - {1} + +# 0 - bytes allowed in prefetch +# 1 - number of messagse. +PREFETCH_SIZE = CHN-1004 : Prefetch Size (bytes) {0,number} : Count {1,number} +# 0 - queue causing flow control +FLOW_ENFORCED = CHN-1005 : Flow Control Enforced (Queue {0}) +FLOW_REMOVED = CHN-1006 : Flow Control Removed +# Channel Transactions +# 0 - time in milliseconds +OPEN_TXN = CHN-1007 : Open Transaction : {0,number} ms +IDLE_TXN = CHN-1008 : Idle Transaction : {0,number} ms + +DISCARDMSG_NOALTEXCH = CHN-1009 : Discarded message : {0,number} as no alternate exchange configured for queue : {1} routing key : {2} +DISCARDMSG_NOROUTE = CHN-1010 : Discarded message : {0,number} as no binding on alternate exchange : {1} +DEADLETTERMSG = CHN-1011 : Message : {0,number} moved to dead letter queue : {1} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/ConfigStore_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/ConfigStore_logmessages.properties new file mode 100644 index 0000000000..541f8b8c68 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/ConfigStore_logmessages.properties @@ -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. +# +# Default File used for all non-defined locales. + +CREATED = CFG-1001 : Created +# 0 - path +STORE_LOCATION = CFG-1002 : Store location : {0} +CLOSE = CFG-1003 : Closed +RECOVERY_START = CFG-1004 : Recovery Start +RECOVERY_COMPLETE = CFG-1005 : Recovery Complete diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Connection_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Connection_logmessages.properties new file mode 100644 index 0000000000..a99bcc7352 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Connection_logmessages.properties @@ -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. +# +# Default File used for all non-defined locales. + +# 0 - Client id +# 1 - Protocol Version +# 2 - Client Version +OPEN = CON-1001 : Open[ : Client ID : {0}][ : Protocol Version : {1}][ : Client Version : {2}] +CLOSE = CON-1002 : Close +IDLE_CLOSE = CON-1003 : Closed due to inactivity diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Exchange_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Exchange_logmessages.properties new file mode 100644 index 0000000000..b25a6a7301 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Exchange_logmessages.properties @@ -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. +# +# Default File used for all non-defined locales. + +# 0 - type +# 1 - name +CREATED = EXH-1001 : Create :[ Durable] Type: {0} Name: {1} +DELETED = EXH-1002 : Deleted +DISCARDMSG = EXH-1003 : Discarded Message : Name: {0} Routing Key: {1} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/ManagementConsole_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/ManagementConsole_logmessages.properties new file mode 100644 index 0000000000..7924be28d3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/ManagementConsole_logmessages.properties @@ -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. +# +# Default File used for all non-defined locales. +# +# 0 - Management Type +STARTUP = MNG-1001 : {0} Management Startup +# 0 - Service +# 1 - Port +LISTENING = MNG-1002 : Starting : {0} : Listening on port {1,number,#} +# 0 - Service +# 1 - Port +SHUTTING_DOWN = MNG-1003 : Shutting down : {0} : port {1,number,#} +# 0 - Management Type +READY = MNG-1004 : {0} Management Ready +# 0 - Management Type +STOPPED = MNG-1005 : {0} Management Stopped +# 0 - Path +SSL_KEYSTORE = MNG-1006 : Using SSL Keystore : {0} +# 0 - Username +OPEN = MNG-1007 : Open : User {0} +# 0 - Username +CLOSE = MNG-1008 : Close : User {0} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/MessageStore_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/MessageStore_logmessages.properties new file mode 100644 index 0000000000..d3823a71a0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/MessageStore_logmessages.properties @@ -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. +# +# Default File used for all non-defined locales. +# +CREATED = MST-1001 : Created +# 0 - path +STORE_LOCATION = MST-1002 : Store location : {0} +CLOSED = MST-1003 : Closed +RECOVERY_START = MST-1004 : Recovery Start +RECOVERED = MST-1005 : Recovered {0,number} messages +RECOVERY_COMPLETE = MST-1006 : Recovery Complete +PASSIVATE = MST-1007 : Store Passivated +OVERFULL = MST-1008 : Store overfull, flow control will be enforced +UNDERFULL = MST-1009 : Store overfull condition cleared diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Queue_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Queue_logmessages.properties new file mode 100644 index 0000000000..538bf994ea --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Queue_logmessages.properties @@ -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. +# +# Default File used for all non-defined locales. +# +# 0 - owner +# 1 - priority +CREATED = QUE-1001 : Create :[ Owner: {0}][ AutoDelete][ Durable][ Transient][ Priority: {1,number,#}] +DELETED = QUE-1002 : Deleted +OVERFULL = QUE-1003 : Overfull : Size : {0,number} bytes, Capacity : {1,number} +UNDERFULL = QUE-1004 : Underfull : Size : {0,number} bytes, Resume Capacity : {1,number} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Subscription_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Subscription_logmessages.properties new file mode 100644 index 0000000000..ef5f885b50 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/Subscription_logmessages.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. +# +# Default File used for all non-defined locales. +# +CREATE = SUB-1001 : Create[ : Durable][ : Arguments : {0}] +CLOSE = SUB-1002 : Close +# 0 - The current subscription state +STATE = SUB-1003 : State : {0} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/TransactionLog_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/TransactionLog_logmessages.properties new file mode 100644 index 0000000000..b9e87159a6 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/TransactionLog_logmessages.properties @@ -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. +# +# Default File used for all non-defined locales. +# +# +CREATED = TXN-1001 : Created +# 0 - path +STORE_LOCATION = TXN-1002 : Store location : {0} +CLOSED = TXN-1003 : Closed +# 0 - queue name +RECOVERY_START = TXN-1004 : Recovery Start[ : {0}] +# 0 - count +# 1 - queue count +RECOVERED = TXN-1005 : Recovered {0,number} messages for queue {1} +# 0 - queue name +RECOVERY_COMPLETE = TXN-1006 : Recovery Complete[ : {0}] +# 0 - xid +# 1 - queue name +XA_INCOMPLETE_QUEUE = TXN-1007 : XA transaction recover for xid {0} incomplete as it references a queue {1} which was not durably retained +# 0 - xid format +# 1 - message id +XA_INCOMPLETE_MESSAGE = TXN-1008 : XA transaction recover for xid {0} incomplete as it references a message {1} which was not durably retained diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/VirtualHost_logmessages.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/VirtualHost_logmessages.properties new file mode 100644 index 0000000000..5695026cbc --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/messages/VirtualHost_logmessages.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# Default File used for all non-defined locales. +# +# 0 - name +CREATED = VHT-1001 : Created : {0} +CLOSED = VHT-1002 : Closed + +STATS_DATA = VHT-1003 : {0} : {1,choice,0#delivered|1#received} : {2,number,#.###} kB/s peak : {3,number,#} bytes total +STATS_MSGS = VHT-1004 : {0} : {1,choice,0#delivered|1#received} : {2,number,#.###} msg/s peak : {3,number,#} msgs total + +ERRORED = VHT-1005 : Unexpected fatal error \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/AbstractLogSubject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/AbstractLogSubject.java new file mode 100644 index 0000000000..baccf240ff --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/AbstractLogSubject.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.logging.subjects; + +import org.apache.qpid.server.logging.LogSubject; + +import java.text.MessageFormat; + +/** + * The LogSubjects all have a similar requriement to format their output and + * provide the String value. + * + * This Abstract LogSubject provides this basic functionality, allowing the + * actual LogSubjects to provide their formating and data. + */ +public abstract class AbstractLogSubject implements LogSubject +{ + private String _logString; + + /** + * Set the toString logging of this LogSubject. Based on a format provided + * by format and the var args. + * @param format The Message to format + * @param args The values to put in to the message. + */ + protected void setLogStringWithFormat(String format, Object... args) + { + _logString = "[" + MessageFormat.format(format, args) + "] "; + } + + /** + * toLogString is how the Logging infrastructure will get the text for this + * LogSubject + * + * @return String representing this LogSubject + */ + public String toLogString() + { + return _logString; + } + + /** + * The logString that will be returned via toLogString + */ + public String getLogString() + { + return _logString; + } + + public void setLogString(String logString) + { + _logString = logString; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/BindingLogSubject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/BindingLogSubject.java new file mode 100644 index 0000000000..a633162e85 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/BindingLogSubject.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.logging.subjects; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.BINDING_FORMAT; + +public class BindingLogSubject extends AbstractLogSubject +{ + + /** + * Create a BindingLogSubject that Logs in the following format. + * + * [ vh(/)/ex(amq.direct)/qu(testQueue)/bd(testQueue) ] + * + * @param routingKey + * @param exchange + * @param queue + */ + public BindingLogSubject(String routingKey, Exchange exchange, + AMQQueue queue) + { + setLogStringWithFormat(BINDING_FORMAT, + queue.getVirtualHost().getName(), + exchange.getType().getType(), + exchange.getName(), + queue.getName(), + routingKey); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ChannelLogSubject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ChannelLogSubject.java new file mode 100644 index 0000000000..5b0e34b73e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ChannelLogSubject.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.logging.subjects; + +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.CHANNEL_FORMAT; + +public class ChannelLogSubject extends AbstractLogSubject +{ + + public ChannelLogSubject(AMQSessionModel session) + { + /** + * LOG FORMAT used by the AMQPConnectorActor follows + * ChannelLogSubject.CHANNEL_FORMAT : con:{0}({1}@{2}/{3})/ch:{4}. + * + * Uses a MessageFormat call to insert the required values according to + * these indices: + * + * 0 - Connection ID + * 1 - User ID + * 2 - IP + * 3 - Virtualhost + * 4 - Channel ID + */ + AMQConnectionModel connection = session.getConnectionModel(); + setLogStringWithFormat(CHANNEL_FORMAT, + connection == null ? -1L : connection.getConnectionId(), + (connection == null || connection.getPrincipalAsString() == null) ? "?" : connection.getPrincipalAsString(), + (connection == null || connection.getRemoteAddressString() == null) ? "?" : connection.getRemoteAddressString(), + (connection == null || connection.getVirtualHostName() == null) ? "?" : connection.getVirtualHostName(), + session.getChannelId()); + + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubject.java new file mode 100644 index 0000000000..87c2377e0f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubject.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.logging.subjects; + +import java.text.MessageFormat; +import org.apache.qpid.server.protocol.AMQConnectionModel; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.CONNECTION_FORMAT; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.SOCKET_FORMAT; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.USER_FORMAT; + +/** The Connection LogSubject */ +public class ConnectionLogSubject extends AbstractLogSubject +{ + + // The Session this Actor is representing + private AMQConnectionModel _session; + + public ConnectionLogSubject(AMQConnectionModel session) + { + _session = session; + } + + // Used to stop re-creating the _logString when we reach our final format + private boolean _upToDate = false; + + /** + * Update the LogString as the Connection process proceeds. + * + * When the Session has an authorized ID add that to the string. + * + * When the Session then gains a Vhost add that to the string, at this point + * we can set upToDate = true as the _logString will not need to be updated + * from this point onwards. + */ + private void updateLogString() + { + if (!_upToDate) + { + if (_session.getPrincipalAsString() != null) + { + if (_session.getVirtualHostName() != null) + { + /** + * LOG FORMAT used by the AMQPConnectorActor follows + * ConnectionLogSubject.CONNECTION_FORMAT : + * con:{0}({1}@{2}/{3}) + * + * Uses a MessageFormat call to insert the required values + * according to these indices: + * + * 0 - Connection ID 1 - User ID 2 - IP 3 - Virtualhost + */ + setLogString("[" + MessageFormat.format(CONNECTION_FORMAT, + _session.getConnectionId(), + _session.getPrincipalAsString(), + _session.getRemoteAddressString(), + _session.getVirtualHostName()) + + "] "); + + _upToDate = true; + } + else + { + setLogString("[" + MessageFormat.format(USER_FORMAT, + _session.getConnectionId(), + _session.getPrincipalAsString(), + _session.getRemoteAddressString()) + + "] "); + + } + } + else + { + setLogString("[" + MessageFormat.format(SOCKET_FORMAT, + _session.getConnectionId(), + _session.getRemoteAddressString()) + + "] "); + } + } + } + + public String toLogString() + { + updateLogString(); + return super.toLogString(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubject.java new file mode 100644 index 0000000000..5affafad75 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubject.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.logging.subjects; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.EXCHANGE_FORMAT; + +public class ExchangeLogSubject extends AbstractLogSubject +{ + + /** Create an ExchangeLogSubject that Logs in the following format. */ + public ExchangeLogSubject(Exchange exchange, VirtualHost vhost) + { + setLogStringWithFormat(EXCHANGE_FORMAT, vhost.getName(), + exchange.getType().getType(), exchange.getName()); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.java new file mode 100644 index 0000000000..7611ee1a88 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.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.logging.subjects; + +/** + * LogSubjectFormat class contains a list of formatting string + * that can be statically imported where needed. + * The formatting strings are to be used via a MessageFormat call + * to insert the required values at the corresponding place holder + * indices. + * + */ + +public class LogSubjectFormat +{ + + private LogSubjectFormat() + { + } + + /** + * LOG FORMAT for the ManagementActors, + * 0 - User ID + * 1 - IP[:Port] + */ + public static final String MANAGEMENT_FORMAT = "mng:{0}({1})"; + + /** + * LOG FORMAT for the Subscription Log Subject + * 0 - Subscription ID + */ + public static final String SUBSCRIPTION_FORMAT = "sub:{0}"; + + /** + * LOG FORMAT for Connection Log Subject - SOCKET format + * 0 - Connection ID + * 1 - Remote Address + */ + public static final String SOCKET_FORMAT = "con:{0}({1})"; + + /** + * LOG FORMAT for Connection Log Subject - USER format + * 0 - Connection ID + * 1 - User ID + * 2 - IP + */ + public static final String USER_FORMAT = "con:{0}({1}@{2})"; + + /** + * LOG FORMAT for the Connection Log Subject - CON format + * 0 - Connection ID + * 1 - User ID + * 2 - IP + * 3 - Virtualhost + */ + public static final String CONNECTION_FORMAT = "con:{0}({1}@{2}/{3})"; + + /** + * LOG FORMAT for the Channel LogSubject + * 0 - Connection ID + * 1 - User ID + * 2 - IP + * 3 - Virtualhost + * 4 - Channel ID + */ + public static final String CHANNEL_FORMAT = CONNECTION_FORMAT + "/ch:{4}"; + + /** + * LOG FORMAT for the Exchange LogSubject, + * 0 - Virtualhost Name + * 1 - Exchange Type + * 2 - Exchange Name + */ + public static final String EXCHANGE_FORMAT = "vh(/{0})/ex({1}/{2})"; + + /** + * LOG FORMAT for a Binding LogSubject + * 0 - Virtualhost Name + * 1 - Exchange Type + * 2 - Exchange Name + * 3 - Queue Name + * 4 - Binding RoutingKey + */ + public static final String BINDING_FORMAT = "vh(/{0})/ex({1}/{2})/qu({3})/rk({4})"; + + /** + * LOG FORMAT for the MessagesStore LogSubject + * 0 - Virtualhost Name + * 1 - Message Store Type + */ + public static final String STORE_FORMAT = "vh(/{0})/ms({1})"; + + /** + * LOG FORMAT for the Queue LogSubject, + * 0 - Virtualhost name + * 1 - queue name + */ + public static final String QUEUE_FORMAT = "vh(/{0})/qu({1})"; +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubject.java new file mode 100644 index 0000000000..ed989d764f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubject.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.logging.subjects; + +import org.apache.qpid.server.virtualhost.VirtualHost; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.STORE_FORMAT; + +public class MessageStoreLogSubject extends AbstractLogSubject +{ + + /** Create an MessageStoreLogSubject that Logs in the following format. */ + public MessageStoreLogSubject(String vhostName, String messageStoreName) + { + setLogStringWithFormat(STORE_FORMAT, vhostName, messageStoreName); + } + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/QueueLogSubject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/QueueLogSubject.java new file mode 100644 index 0000000000..53a9ab75d9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/QueueLogSubject.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.logging.subjects; + +import org.apache.qpid.server.queue.AMQQueue; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.QUEUE_FORMAT; + +public class QueueLogSubject extends AbstractLogSubject +{ + + /** Create an QueueLogSubject that Logs in the following format. */ + public QueueLogSubject(AMQQueue queue) + { + setLogStringWithFormat(QUEUE_FORMAT, + queue.getVirtualHost().getName(), + queue.getName()); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubject.java new file mode 100644 index 0000000000..9a23b733dc --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubject.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.logging.subjects; + +import org.apache.qpid.server.subscription.Subscription; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.SUBSCRIPTION_FORMAT; + +import java.text.MessageFormat; + +public class SubscriptionLogSubject extends AbstractLogSubject +{ + + /** + * Create an QueueLogSubject that Logs in the following format. + * + * @param subscription + */ + public SubscriptionLogSubject(Subscription subscription) + { + // Delegate the formating of the Queue to the QueueLogSubject. So final + // log string format is: + // [ sub:(vh()/qu()) ] + + String queueString = new QueueLogSubject(subscription.getQueue()).toLogString(); + + setLogString("[" + MessageFormat.format(SUBSCRIPTION_FORMAT, + subscription.getSubscriptionID()) + + "(" + // queueString is [vh(/{0})/qu({1}) ] so need to trim + // ^ ^^ + + queueString.substring(1,queueString.length() - 3) + + ")" + + "] "); + + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/AMQMessageHeader.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/AMQMessageHeader.java new file mode 100644 index 0000000000..63bd1e45a0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/AMQMessageHeader.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.message; + +import java.util.Collection; +import java.util.Set; + +public interface AMQMessageHeader +{ + String getCorrelationId(); + + long getExpiration(); + + String getUserId(); + + String getAppId(); + + String getMessageId(); + + String getMimeType(); + + String getEncoding(); + + byte getPriority(); + + long getTimestamp(); + + String getType(); + + String getReplyTo(); + + String getReplyToExchange(); + String getReplyToRoutingKey(); + + + Object getHeader(String name); + + boolean containsHeaders(Set names); + + boolean containsHeader(String name); + + Collection getHeaderNames(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/AbstractServerMessageImpl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/AbstractServerMessageImpl.java new file mode 100644 index 0000000000..8311dbd5ff --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/AbstractServerMessageImpl.java @@ -0,0 +1,111 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.message; + +import org.apache.qpid.server.store.StorableMessageMetaData; +import org.apache.qpid.server.store.StoredMessage; + +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +public abstract class AbstractServerMessageImpl implements ServerMessage +{ + + private static final AtomicIntegerFieldUpdater _refCountUpdater = + AtomicIntegerFieldUpdater.newUpdater(AbstractServerMessageImpl.class, "_referenceCount"); + + private volatile int _referenceCount = 0; + private final StoredMessage _handle; + + public AbstractServerMessageImpl(StoredMessage handle) + { + _handle = handle; + } + + public StoredMessage getStoredMessage() + { + return _handle; + } + + public boolean incrementReference() + { + return incrementReference(1); + } + + public boolean incrementReference(int count) + { + if(_refCountUpdater.addAndGet(this, count) <= 0) + { + _refCountUpdater.addAndGet(this, -count); + return false; + } + else + { + return true; + } + } + + /** + * Threadsafe. This will decrement the reference count and when it reaches zero will remove the message from the + * message store. + * + */ + public void decrementReference() + { + int count = _refCountUpdater.decrementAndGet(this); + + // note that the operation of decrementing the reference count and then removing the message does not + // have to be atomic since the ref count starts at 1 and the exchange itself decrements that after + // the message has been passed to all queues. i.e. we are + // not relying on the all the increments having taken place before the delivery manager decrements. + if (count == 0) + { + // set the reference count way below 0 so that we can detect that the message has been deleted + // this is to guard against the message being spontaneously recreated (from the mgmt console) + // by copying from other queues at the same time as it is being removed. + _refCountUpdater.set(this,Integer.MIN_VALUE/2); + + // 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 (_handle != null) + { + _handle.remove(); + } + } + else + { + if (count < 0) + { + throw new RuntimeException("Reference count for message id " + debugIdentity() + + " has gone below 0."); + } + } + } + + public String debugIdentity() + { + return "(HC:" + System.identityHashCode(this) + " ID:" + getMessageNumber() + " Ref:" + getReferenceCount() + ")"; + } + + protected int getReferenceCount() + { + return _referenceCount; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/EnqueableMessage.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/EnqueableMessage.java new file mode 100755 index 0000000000..7be91ad0ca --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/EnqueableMessage.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.message; + +import org.apache.qpid.server.store.StoredMessage; + +public interface EnqueableMessage +{ + long getMessageNumber(); + boolean isPersistent(); + StoredMessage getStoredMessage(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/InboundMessage.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/InboundMessage.java new file mode 100644 index 0000000000..1b3fdb1870 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/InboundMessage.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.message; + + +import org.apache.qpid.server.queue.Filterable; + +public interface InboundMessage extends Filterable +{ + String getRoutingKey(); + + AMQMessageHeader getMessageHeader(); + + boolean isPersistent(); + + boolean isRedelivered(); + + long getSize(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/MessageContentSource.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/MessageContentSource.java new file mode 100755 index 0000000000..44741f57bd --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/MessageContentSource.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.message; + +import java.nio.ByteBuffer; + +public interface MessageContentSource +{ + public int getContent(ByteBuffer buf, int offset); + public ByteBuffer getContent(int offset, int size); + + long getSize(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/MessageReference.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/MessageReference.java new file mode 100644 index 0000000000..399f8f9327 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/MessageReference.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.message; + +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class MessageReference +{ + + private final AtomicBoolean _released = new AtomicBoolean(false); + + private volatile M _message; + + public MessageReference(M message) + { + _message = message; + onReference(message); + } + + abstract protected void onReference(M message); + + abstract protected void onRelease(M message); + + public M getMessage() + { + return _message; + } + + public void release() + { + if(!_released.getAndSet(true)) + { + if(_message != null) + { + onRelease(_message); + } + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/ServerMessage.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/ServerMessage.java new file mode 100644 index 0000000000..e1ad2fd0ca --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/message/ServerMessage.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.message; + +import org.apache.qpid.server.store.StorableMessageMetaData; +import org.apache.qpid.server.store.StoredMessage; + +import java.nio.ByteBuffer; + +public interface ServerMessage extends EnqueableMessage, MessageContentSource +{ + String getRoutingKey(); + + AMQMessageHeader getMessageHeader(); + + public StoredMessage getStoredMessage(); + + boolean isPersistent(); + + long getSize(); + + boolean isImmediate(); + + long getExpiration(); + + MessageReference newReference(); + + long getMessageNumber(); + + long getArrivalTime(); + + public int getContent(ByteBuffer buf, int offset); + + public ByteBuffer getContent(int offset, int size); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AccessControlProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AccessControlProvider.java new file mode 100644 index 0000000000..d96bef0463 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AccessControlProvider.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.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.apache.qpid.server.security.AccessControl; + +public interface AccessControlProvider extends ConfiguredObject +{ + public static final String ID = "id"; + public static final String DESCRIPTION = "description"; + public static final String NAME = "name"; + public static final String STATE = "state"; + public static final String DURABLE = "durable"; + public static final String LIFETIME_POLICY = "lifetimePolicy"; + public static final String TIME_TO_LIVE = "timeToLive"; + public static final String CREATED = "created"; + public static final String UPDATED = "updated"; + public static final String TYPE = "type"; + + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList(ID, + NAME, + DESCRIPTION, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + TYPE)); + + //retrieve the underlying AccessControl object + AccessControl getAccessControl(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Attribute.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Attribute.java new file mode 100644 index 0000000000..4fccf47e0e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Attribute.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.model; + +public abstract class Attribute +{ + private final String _name; + public Attribute(String name) + { + _name = name; + } + + public String getName() + { + return _name; + } + + abstract public Class getType(); + + public T getValue(C configuredObject) + { + Object o = configuredObject.getAttribute(_name); + if(getType().isInstance(o)) + { + return (T) o; + } + return null; + } + + public T setValue(T expected, T desired, C configuredObject) + { + return (T) configuredObject.setAttribute(_name, expected, desired); + } + + abstract public T setValue(String stringValue, C configuredObject); + + static class StringAttribute extends Attribute + { + + public StringAttribute(String name) + { + super(name); + } + + @Override + public Class getType() + { + return String.class; + } + + @Override + public String setValue(String stringValue, C configuredObject) + { + return setValue(getValue(configuredObject), stringValue, configuredObject); + } + + } + + static class IntegerAttribute extends Attribute + { + + public IntegerAttribute(String name) + { + super(name); + } + + @Override + public Class getType() + { + return Integer.class; + } + + @Override + public Integer setValue(String stringValue, C configuredObject) + { + try + { + Integer val = Integer.valueOf(stringValue); + return setValue(getValue(configuredObject), val, configuredObject); + } + catch (NumberFormatException e) + { + throw new IllegalArgumentException(e); + } + } + } + + + static class LongAttribute extends Attribute + { + + public LongAttribute(String name) + { + super(name); + } + + @Override + public Class getType() + { + return Long.class; + } + + @Override + public Long setValue(String stringValue, C configuredObject) + { + try + { + Long val = Long.valueOf(stringValue); + return setValue(getValue(configuredObject), val, configuredObject); + } + catch (NumberFormatException e) + { + throw new IllegalArgumentException(e); + } + } + } + + + static class DoubleAttribute extends Attribute + { + + public DoubleAttribute(String name) + { + super(name); + } + + @Override + public Class getType() + { + return Double.class; + } + + @Override + public Double setValue(String stringValue, C configuredObject) + { + try + { + Double val = Double.valueOf(stringValue); + return setValue(getValue(configuredObject), val, configuredObject); + } + catch (NumberFormatException e) + { + throw new IllegalArgumentException(e); + } + } + } + + + static class FloatAttribute extends Attribute + { + + public FloatAttribute(String name) + { + super(name); + } + + @Override + public Class getType() + { + return Float.class; + } + + @Override + public Float setValue(String stringValue, C configuredObject) + { + try + { + Float val = Float.valueOf(stringValue); + return setValue(getValue(configuredObject), val, configuredObject); + } + catch (NumberFormatException e) + { + throw new IllegalArgumentException(e); + } + } + } + + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AuthenticationMethod.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AuthenticationMethod.java new file mode 100644 index 0000000000..7a5927a365 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AuthenticationMethod.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.model; + +import java.util.Collection; + +public interface AuthenticationMethod extends ConfiguredObject +{ + // name is the SASL mech where this is a SASL authentication + + // parents + VirtualHostAlias getVirtualHostAlias(); + AuthenticationProvider getAuthenticationProvider(); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AuthenticationProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AuthenticationProvider.java new file mode 100644 index 0000000000..f75d0211cb --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/AuthenticationProvider.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.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.apache.qpid.server.security.SubjectCreator; + +public interface AuthenticationProvider extends ConfiguredObject +{ + + public static final String ID = "id"; + public static final String DESCRIPTION = "description"; + public static final String NAME = "name"; + public static final String STATE = "state"; + public static final String DURABLE = "durable"; + public static final String LIFETIME_POLICY = "lifetimePolicy"; + public static final String TIME_TO_LIVE = "timeToLive"; + public static final String CREATED = "created"; + public static final String UPDATED = "updated"; + + public static final String TYPE = "type"; + + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList(ID, + NAME, + DESCRIPTION, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + TYPE + )); + //children + Collection getVirtualHostPortBindings(); + + String getName(); + + /** + * A temporary method to create SubjectCreator. + * + * TODO: move all the functionality from SubjectCreator into AuthenticationProvider + */ + SubjectCreator getSubjectCreator(); + + /** + * Returns the preferences provider associated with this authentication provider + * @return PreferencesProvider + */ + PreferencesProvider getPreferencesProvider(); + + /** + * Sets the preferences provider + * @param preferencesProvider + */ + void setPreferencesProvider(PreferencesProvider preferencesProvider); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Binding.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Binding.java new file mode 100644 index 0000000000..fdb009386c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Binding.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.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public interface Binding extends ConfiguredObject +{ + + public String MATCHED_BYTES = "matchedBytes"; + public String MATCHED_MESSAGES = "matchedMessages"; + public String STATE_CHANGED = "stateChanged"; + + public static final Collection AVAILABLE_STATISTICS = + Collections.unmodifiableCollection( + Arrays.asList( + MATCHED_BYTES, + MATCHED_MESSAGES, + STATE_CHANGED)); + + + public String ARGUMENTS = "arguments"; + public String CREATED = "created"; + public String DURABLE = "durable"; + public String ID = "id"; + public String LIFETIME_POLICY = "lifetimePolicy"; + public String NAME = "name"; + public String STATE = "state"; + public String TIME_TO_LIVE = "timeToLive"; + public String UPDATED = "updated"; + public String QUEUE = "queue"; + public String EXCHANGE = "exchange"; + + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableCollection( + Arrays.asList(ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + EXCHANGE, + QUEUE, + ARGUMENTS) + ); + + + + Map getArguments(); + + void delete(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java new file mode 100644 index 0000000000..0c1a6de58b --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java @@ -0,0 +1,190 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model; + +import java.net.SocketAddress; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import org.apache.qpid.server.logging.LogRecorder; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public interface Broker extends ConfiguredObject +{ + + String BUILD_VERSION = "buildVersion"; + String BYTES_RETAINED = "bytesRetained"; + String OPERATING_SYSTEM = "operatingSystem"; + String PLATFORM = "platform"; + String PROCESS_PID = "processPid"; + String PRODUCT_VERSION = "productVersion"; + String SUPPORTED_BROKER_STORE_TYPES = "supportedBrokerStoreTypes"; + String SUPPORTED_VIRTUALHOST_TYPES = "supportedVirtualHostTypes"; + String SUPPORTED_VIRTUALHOST_STORE_TYPES = "supportedVirtualHostStoreTypes"; + String SUPPORTED_AUTHENTICATION_PROVIDERS = "supportedAuthenticationProviders"; + String SUPPORTED_PREFERENCES_PROVIDERS_TYPES = "supportedPreferencesProviderTypes"; + String CREATED = "created"; + String DURABLE = "durable"; + String ID = "id"; + String LIFETIME_POLICY = "lifetimePolicy"; + String NAME = "name"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String UPDATED = "updated"; + String DEFAULT_VIRTUAL_HOST = "defaultVirtualHost"; + String STATISTICS_REPORTING_PERIOD = "statisticsReportingPeriod"; + String STATISTICS_REPORTING_RESET_ENABLED = "statisticsReportingResetEnabled"; + String STORE_TYPE = "storeType"; + String STORE_VERSION = "storeVersion"; + String STORE_PATH = "storePath"; + String MODEL_VERSION = "modelVersion"; + + String QUEUE_ALERT_THRESHOLD_MESSAGE_AGE = "queue.alertThresholdMessageAge"; + String QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES = "queue.alertThresholdQueueDepthMessages"; + String QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES = "queue.alertThresholdQueueDepthBytes"; + String QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE = "queue.alertThresholdMessageSize"; + String QUEUE_ALERT_REPEAT_GAP = "queue.alertRepeatGap"; + String QUEUE_FLOW_CONTROL_SIZE_BYTES = "queue.flowControlSizeBytes"; + String QUEUE_FLOW_CONTROL_RESUME_SIZE_BYTES = "queue.flowResumeSizeBytes"; + String QUEUE_MAXIMUM_DELIVERY_ATTEMPTS = "queue.maximumDeliveryAttempts"; + String QUEUE_DEAD_LETTER_QUEUE_ENABLED = "queue.deadLetterQueueEnabled"; + + String CONNECTION_SESSION_COUNT_LIMIT = "connection.sessionCountLimit"; + String CONNECTION_HEART_BEAT_DELAY = "connection.heartBeatDelay"; + String CONNECTION_CLOSE_WHEN_NO_ROUTE = "connection.closeWhenNoRoute"; + + String VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD = "virtualhost.housekeepingCheckPeriod"; + String VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE = "virtualhost.storeTransactionIdleTimeoutClose"; + String VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_WARN = "virtualhost.storeTransactionIdleTimeoutWarn"; + String VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE = "virtualhost.storeTransactionOpenTimeoutClose"; + String VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_WARN = "virtualhost.storeTransactionOpenTimeoutWarn"; + + // Attributes + Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList(BUILD_VERSION, + BYTES_RETAINED, + OPERATING_SYSTEM, + PLATFORM, + PROCESS_PID, + PRODUCT_VERSION, + SUPPORTED_BROKER_STORE_TYPES, + SUPPORTED_VIRTUALHOST_STORE_TYPES, + SUPPORTED_AUTHENTICATION_PROVIDERS, + SUPPORTED_PREFERENCES_PROVIDERS_TYPES, + CREATED, + DURABLE, + ID, + LIFETIME_POLICY, + NAME, + STATE, + TIME_TO_LIVE, + UPDATED, + DEFAULT_VIRTUAL_HOST, + QUEUE_ALERT_THRESHOLD_MESSAGE_AGE, + QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, + QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, + QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE, + QUEUE_ALERT_REPEAT_GAP, + QUEUE_FLOW_CONTROL_SIZE_BYTES, + QUEUE_FLOW_CONTROL_RESUME_SIZE_BYTES, + QUEUE_MAXIMUM_DELIVERY_ATTEMPTS, + QUEUE_DEAD_LETTER_QUEUE_ENABLED, + VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD, + CONNECTION_SESSION_COUNT_LIMIT, + CONNECTION_HEART_BEAT_DELAY, + CONNECTION_CLOSE_WHEN_NO_ROUTE, + STATISTICS_REPORTING_PERIOD, + STATISTICS_REPORTING_RESET_ENABLED, + STORE_TYPE, + STORE_VERSION, + STORE_PATH, + MODEL_VERSION, + VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE, + VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_WARN, + VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE, + VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_WARN + )); + + //children + Collection < VirtualHost > getVirtualHosts(); + + Collection getPorts(); + + Collection getAuthenticationProviders(); + + Collection getAccessControlProviders(); + + Collection getGroupProviders(); + + /** + * A temporary hack to expose root message logger via broker instance. + * TODO We need a better way to do operational logging, for example, via logging listeners + */ + RootMessageLogger getRootMessageLogger(); + + /** + * A temporary hack to expose security manager via broker instance. + * TODO We need to add and implement an authorization provider configured object instead + */ + SecurityManager getSecurityManager(); + + /** + * TODO: A temporary hack to expose log recorder via broker instance. + */ + LogRecorder getLogRecorder(); + + AuthenticationProvider findAuthenticationProviderByName(String authenticationProviderName); + + VirtualHost findVirtualHostByName(String name); + + KeyStore findKeyStoreByName(String name); + + TrustStore findTrustStoreByName(String name); + + /** + * Get the SubjectCreator for the given socket address. + * TODO: move the authentication related functionality into host aliases and AuthenticationProviders + * + * @param address The (listening) socket address for which the AuthenticationManager is required + */ + SubjectCreator getSubjectCreator(SocketAddress localAddress); + + Collection getKeyStores(); + + Collection getTrustStores(); + + /* + * TODO: Remove this method. Eventually the broker will become a registry. + */ + VirtualHostRegistry getVirtualHostRegistry(); + + TaskExecutor getTaskExecutor(); + + boolean isManagementMode(); + + AuthenticationProvider getAuthenticationProvider(SocketAddress localAddress); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfigurationChangeListener.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfigurationChangeListener.java new file mode 100644 index 0000000000..d20c709e90 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfigurationChangeListener.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.model; + +public interface ConfigurationChangeListener +{ + /** + * Inform the listener that the passed object has changed state + * + * @param object the object whose state has changed + * @param oldState the state prior to the change + * @param newState the state after the change + */ + void stateChanged(ConfiguredObject object, State oldState, State newState); + + void childAdded(ConfiguredObject object, ConfiguredObject child); + + void childRemoved(ConfiguredObject object, ConfiguredObject child); + + void attributeSet(ConfiguredObject object, String attributeName, Object oldAttributeValue, Object newAttributeValue); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObject.java new file mode 100644 index 0000000000..45e743dbca --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObject.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.model; + +import java.security.AccessControlException; +import java.util.Collection; +import java.util.Map; +import java.util.UUID; + +/** + * An object that can be "managed" (eg via the web interface) and usually read from configuration. + */ +public interface ConfiguredObject +{ + + /** + * Get the universally unique identifier for the object + * + * @return the objects id + */ + UUID getId(); + + /** + * Get the name of the object + * + * @return the name of the object + */ + String getName(); + + + /** + * Attempt to change the name of the object + * + * Request a change to the name of the object. The caller must pass in the name it believes the object currently + * has. If the current name differs from this expected value, then no name change will occur + * + * @param currentName the name the caller believes the object to have + * @param desiredName the name the caller would like the object to have + * @return the new name for the object + * @throws IllegalStateException if the name of the object may not be changed in in the current state + * @throws AccessControlException if the current context does not have permission to change the name + * @throws IllegalArgumentException if the provided name is not legal + * @throws NullPointerException if the desired name is null + */ + String setName(String currentName, String desiredName) throws IllegalStateException, + AccessControlException; + + + /** + * Get the desired state of the object. + * + * This is the state set at the object itself, however the object + * may not be able attain this state if one of its ancestors is in a different state (in particular a descendant + * object may not be ACTIVE if all of its ancestors are not also ACTIVE). + * + * @return the desired state of the object + */ + State getDesiredState(); + + /** + * Change the desired state of the object + * + * Request a change to the current state. The caller must pass in the state it believe the object to be in, if + * this differs from the current desired state when the object evalues the request, then no state change will occur. + * + * @param currentState the state the caller believes the object to be in + * @param desiredState the state the caller wishes the object to attain + * @return the new current state + * @throws IllegalStateTransitionException the requested state tranisition is invalid + * @throws AccessControlException the current context does not have sufficient permissions to change the state + */ + State setDesiredState(State currentState, State desiredState) throws IllegalStateTransitionException, + AccessControlException; + + /** + * Get the actual state of the object. + * + * This state is derived from the desired state of the object itself and + * the actual state of its parents. If an object "desires" to be ACTIVE, but one of its parents is STOPPED, then + * the actual state of the object will be STOPPED + * + * @return the actual state of the object + */ + State getActualState(); + + + /** + * Add a listener which will be informed of all changes to this configuration object + * + * @param listener the listener to add + */ + void addChangeListener(ConfigurationChangeListener listener); + + /** + * Remove a change listener + * + * + * @param listener the listener to remove + * @return true iff a listener was removed + */ + boolean removeChangeListener(ConfigurationChangeListener listener); + + /** + * Get the parent of the given type for this object + * + * @param clazz the class of parent being asked for + * @return the objects parent + */ + T getParent(Class clazz); + + + /** + * Returns whether the the object configuration is durably stored + * + * @return the durability + */ + boolean isDurable(); + + /** + * Sets the durability of the object + * + * @param durable true iff the caller wishes the object to store its configuration durably + * + * @throws IllegalStateException if the durability cannot be changed in the current state + * @throws AccessControlException if the current context does not have sufficient permission to change the durability + * @throws IllegalArgumentException if the object does not support the requested durability + */ + void setDurable(boolean durable) throws IllegalStateException, + AccessControlException, + IllegalArgumentException; + + /** + * Return the lifetime policy for the object + * + * @return the lifetime policy + */ + LifetimePolicy getLifetimePolicy(); + + /** + * Set the lifetime policy of the object + * + * @param expected The lifetime policy the caller believes the object currently has + * @param desired The lifetime policy the caller desires the object to have + * @return the new lifetime policy + * @throws IllegalStateException if the lifetime policy cannot be changed in the current state + * @throws AccessControlException if the caller does not have permission to change the lifetime policy + * @throws IllegalArgumentException if the object does not support the requested lifetime policy + */ + LifetimePolicy setLifetimePolicy(LifetimePolicy expected, LifetimePolicy desired) throws IllegalStateException, + AccessControlException, + IllegalArgumentException; + + /** + * Get the time the object will live once the lifetime policy conditions are no longer fulfilled + * + * @return the time to live + */ + long getTimeToLive(); + + /** + * Set the ttl value + * + * @param expected the ttl the caller believes the object currently has + * @param desired the ttl value the caller + * @return the new ttl value + * @throws IllegalStateException if the ttl cannot be set in the current state + * @throws AccessControlException if the caller does not have permission to change the ttl + * @throws IllegalArgumentException if the object does not support the requested ttl value + */ + long setTimeToLive(long expected, long desired) throws IllegalStateException, + AccessControlException, + IllegalArgumentException; + + /** + * Get the names of attributes that are set on this object + * + * Note that the returned collection is correct at the time the method is called, but will not reflect future + * additions or removals when they occur + * + * @return the collection of attribute names + */ + Collection getAttributeNames(); + + + /** + * Return the value for the given attribute name. The actual attribute value + * is returned if the configured object has such attribute set. If not, the + * value is looked default attributes. + * + * @param name + * the name of the attribute + * @return the value of the attribute at the object (or null if the + * attribute value is set neither on object itself no in defaults) + */ + Object getAttribute(String name); + + /** + * Return the map containing only explicitly set attributes + * + * @return the map with the attributes + */ + Map getActualAttributes(); + + /** + * Set the value of an attribute + * + * @param name the name of the attribute to be set + * @param expected the value the caller believes the attribute currently has (or null if it is expected to be unset) + * @param desired the desired value for the attribute (or null to unset the attribute) + * @return the new value for the given attribute + * @throws IllegalStateException if the attribute cannot be set while the object is in its current state + * @throws AccessControlException if the caller does not have permission to alter the value of the attribute + * @throws IllegalArgumentException if the provided value is not valid for the given argument + */ + Object setAttribute(String name, Object expected, Object desired) throws IllegalStateException, + AccessControlException, + IllegalArgumentException; + + + /** + * Return the Statistics holder for the ConfiguredObject + * + * @return the Statistics holder for the ConfiguredObject (or null if none exists) + */ + Statistics getStatistics(); + + /** + * Return children of the ConfiguredObject of the given class + * + * @param clazz the class of the children to return + * @return the children + * + * @throws NullPointerException if the supplied class null + * + */ + Collection getChildren(Class clazz); + + + C createChild(Class childClass, + Map attributes, + ConfiguredObject... otherParents); + + void setAttributes(Map attributes) throws IllegalStateException, AccessControlException, IllegalArgumentException; +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectFinder.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectFinder.java new file mode 100644 index 0000000000..6a7d6f8f7b --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectFinder.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.model; + +import java.util.Collection; + +public class ConfiguredObjectFinder +{ + public static C findConfiguredObjectByName(Collection configuredObjects, String name) + { + for (C configuredObject : configuredObjects) + { + if (name.equals(configuredObject.getName())) + { + return configuredObject; + } + } + return null; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Connection.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Connection.java new file mode 100644 index 0000000000..3139850892 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Connection.java @@ -0,0 +1,112 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public interface Connection extends ConfiguredObject +{ + + // Statistics + + String BYTES_IN = "bytesIn"; + String BYTES_OUT = "bytesOut"; + String LAST_IO_TIME = "lastIoTime"; + String LOCAL_TRANSACTION_BEGINS = "localTransactionBegins"; + String LOCAL_TRANSACTION_ROLLBACKS = "localTransactionRollbacks"; + String MESSAGES_IN = "messagesIn"; + String MESSAGES_OUT = "messagesOut"; + String SESSION_COUNT = "sessionCount"; + String STATE_CHANGED = "stateChanged"; + String XA_TRANSACTION_BRANCH_ENDS = "xaTransactionBranchEnds"; + String XA_TRANSACTION_BRANCH_STARTS = "xaTransactionBranchStarts"; + String XA_TRANSACTION_BRANCH_SUSPENDS = "xaTransactionBranchSuspends"; + + public static final Collection AVAILABLE_STATISTICS = + Collections.unmodifiableCollection( + Arrays.asList(BYTES_IN, + BYTES_OUT, + LAST_IO_TIME, + LOCAL_TRANSACTION_BEGINS, + LOCAL_TRANSACTION_ROLLBACKS, + MESSAGES_IN, + MESSAGES_OUT, + SESSION_COUNT, + STATE_CHANGED, + XA_TRANSACTION_BRANCH_ENDS, + XA_TRANSACTION_BRANCH_STARTS, + XA_TRANSACTION_BRANCH_SUSPENDS)); + + // Attributes + + public static final String ID = "id"; + public static final String NAME = "name"; + public static final String STATE = "state"; + public static final String DURABLE = "durable"; + public static final String LIFETIME_POLICY = "lifetimePolicy"; + public static final String TIME_TO_LIVE = "timeToLive"; + public static final String CREATED = "created"; + public static final String UPDATED = "updated"; + + public static final String CLIENT_ID = "clientId"; + public static final String CLIENT_VERSION = "clientVersion"; + public static final String INCOMING = "incoming"; + public static final String LOCAL_ADDRESS = "localAddress"; + public static final String PRINCIPAL = "principal"; + public static final String PROPERTIES = "properties"; + public static final String REMOTE_ADDRESS = "remoteAddress"; + public static final String REMOTE_PROCESS_NAME = "remoteProcessName"; + public static final String REMOTE_PROCESS_PID = "remoteProcessPid"; + public static final String SESSION_COUNT_LIMIT = "sessionCountLimit"; + public static final String TRANSPORT = "transport"; + public static final String PORT = "port"; + + + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableCollection( + Arrays.asList( ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + CLIENT_ID, + CLIENT_VERSION, + INCOMING, + LOCAL_ADDRESS, + PRINCIPAL, + PROPERTIES, + REMOTE_ADDRESS, + REMOTE_PROCESS_NAME, + REMOTE_PROCESS_PID, + SESSION_COUNT_LIMIT, + TRANSPORT, + PORT)); + + //children + Collection getSessions(); + + void delete(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Consumer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Consumer.java new file mode 100644 index 0000000000..958177e713 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Consumer.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.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public interface Consumer extends ConfiguredObject +{ + public String DISTRIBUTION_MODE = "distributionMode"; + public String EXCLUSIVE = "exclusive"; + public String NO_LOCAL = "noLocal"; + public String SELECTOR = "selector"; + public String SETTLEMENT_MODE = "settlementMode"; + public String CREATED = "created"; + public String DURABLE = "durable"; + public String ID = "id"; + public String LIFETIME_POLICY = "lifetimePolicy"; + public String NAME = "name"; + public String STATE = "state"; + public String TIME_TO_LIVE = "timeToLive"; + public String UPDATED = "updated"; + + public Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableCollection( + Arrays.asList(ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + DISTRIBUTION_MODE, + SETTLEMENT_MODE, + EXCLUSIVE, + NO_LOCAL, + SELECTOR)); + + public String BYTES_OUT = "bytesOut"; + public String MESSAGES_OUT = "messagesOut"; + public String STATE_CHANGED = "stateChanged"; + public String UNACKNOWLEDGED_BYTES = "unacknowledgedBytes"; + public String UNACKNOWLEDGED_MESSAGES = "unacknowledgedMessages"; + + public Collection AVAILABLE_STATISTICS = + Collections.unmodifiableCollection( + Arrays.asList(BYTES_OUT, + MESSAGES_OUT, + STATE_CHANGED, + UNACKNOWLEDGED_BYTES, + UNACKNOWLEDGED_MESSAGES) + ); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Event.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Event.java new file mode 100644 index 0000000000..91b684f06e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Event.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.model; + +abstract public class Event +{ + abstract public T getEventType(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/EventType.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/EventType.java new file mode 100644 index 0000000000..edd5ce4250 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/EventType.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.model; + +import java.util.HashMap; +import java.util.Map; + +/** + * A type of event generated by a ConfiguredObject. + */ +public abstract class EventType> +{ + private static final Map, Integer> EVENT_TYPES = + new HashMap, Integer>(); + + private final int _classId; + + protected EventType() + { + synchronized (EVENT_TYPES) + { + if(EVENT_TYPES.containsKey(getClass())) + { + throw new IllegalArgumentException("Cannot define more one instance of the same EventType " + + getClass().getName()); + } + else + { + _classId = EVENT_TYPES.size(); + EVENT_TYPES.put(getClass(), _classId); + } + } + } + + public final int getId() + { + return _classId; + } + + abstract public Event newEvent(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Exchange.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Exchange.java new file mode 100644 index 0000000000..e63c71e955 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Exchange.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.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public interface Exchange extends ConfiguredObject +{ + String BINDING_COUNT = "bindingCount"; + String BYTES_DROPPED = "bytesDropped"; + String BYTES_IN = "bytesIn"; + String MESSAGES_DROPPED = "messagesDropped"; + String MESSAGES_IN = "messagesIn"; + String PRODUCER_COUNT = "producerCount"; + String STATE_CHANGED = "stateChanged"; + + public static final Collection AVAILABLE_STATISTICS = + Collections.unmodifiableList( + Arrays.asList(BINDING_COUNT, + BYTES_DROPPED, + BYTES_IN, + MESSAGES_DROPPED, + MESSAGES_IN, + PRODUCER_COUNT, + STATE_CHANGED)); + + String CREATED = "created"; + String DURABLE = "durable"; + String ID = "id"; + String LIFETIME_POLICY = "lifetimePolicy"; + String NAME = "name"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String UPDATED = "updated"; + String ALTERNATE_EXCHANGE = "alternateExchange"; + String TYPE = "type"; + + // Attributes + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + ALTERNATE_EXCHANGE, + TYPE + )); + + String getExchangeType(); + + //children + Collection getBindings(); + Collection getPublishers(); + + //operations + Binding createBinding(String bindingKey, + Queue queue, + Map bindingArguments, + Map attributes); + + + // Statistics + + void delete(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Group.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Group.java new file mode 100644 index 0000000000..aacd515107 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Group.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.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public interface Group extends ConfiguredObject +{ + String CREATED = "created"; + String DURABLE = "durable"; + String ID = "id"; + String LIFETIME_POLICY = "lifetimePolicy"; + String NAME = "name"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String UPDATED = "updated"; + + // Attributes + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED + )); + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/GroupMember.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/GroupMember.java new file mode 100644 index 0000000000..6832cc6fa6 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/GroupMember.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.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public interface GroupMember extends ConfiguredObject +{ + String CREATED = "created"; + String DURABLE = "durable"; + String ID = "id"; + String LIFETIME_POLICY = "lifetimePolicy"; + String NAME = "name"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String UPDATED = "updated"; + + // Attributes + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED + )); + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/GroupProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/GroupProvider.java new file mode 100644 index 0000000000..9016f97927 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/GroupProvider.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.model; + +import java.security.Principal; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +public interface GroupProvider extends ConfiguredObject +{ + public static final String ID = "id"; + public static final String DESCRIPTION = "description"; + public static final String NAME = "name"; + public static final String STATE = "state"; + public static final String DURABLE = "durable"; + public static final String LIFETIME_POLICY = "lifetimePolicy"; + public static final String TIME_TO_LIVE = "timeToLive"; + public static final String CREATED = "created"; + public static final String UPDATED = "updated"; + public static final String TYPE = "type"; + + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList(ID, + NAME, + DESCRIPTION, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + TYPE)); + + Set getGroupPrincipalsForUser(String username); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/IllegalStateTransitionException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/IllegalStateTransitionException.java new file mode 100644 index 0000000000..9cab5e2103 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/IllegalStateTransitionException.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.model; + +public class IllegalStateTransitionException extends RuntimeException +{ + public IllegalStateTransitionException() + { + } + + public IllegalStateTransitionException(final String message) + { + super(message); + } + + public IllegalStateTransitionException(final String message, final Throwable cause) + { + super(message, cause); + } + + public IllegalStateTransitionException(final Throwable cause) + { + super(cause); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/IntegrityViolationException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/IntegrityViolationException.java new file mode 100644 index 0000000000..def450640a --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/IntegrityViolationException.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.model; + +public class IntegrityViolationException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public IntegrityViolationException(String message, Throwable cause) + { + super(message, cause); + } + + public IntegrityViolationException(String message) + { + super(message); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/KeyStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/KeyStore.java new file mode 100644 index 0000000000..ab909390bd --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/KeyStore.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.model; + +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import javax.net.ssl.KeyManager; + +public interface KeyStore extends ConfiguredObject +{ + String ID = "id"; + String NAME = "name"; + String DURABLE = "durable"; + String LIFETIME_POLICY = "lifetimePolicy"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String CREATED = "created"; + String UPDATED = "updated"; + String DESCRIPTION = "description"; + + String PATH = "path"; + String PASSWORD = "password"; + String TYPE = "type"; + String CERTIFICATE_ALIAS = "certificateAlias"; + String KEY_MANAGER_FACTORY_ALGORITHM = "keyManagerFactoryAlgorithm"; + + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + DESCRIPTION, + PATH, + PASSWORD, + TYPE, + CERTIFICATE_ALIAS, + KEY_MANAGER_FACTORY_ALGORITHM + )); + + public String getPassword(); + + public void setPassword(String password); + + public KeyManager[] getKeyManagers() throws GeneralSecurityException; + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/LifetimePolicy.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/LifetimePolicy.java new file mode 100644 index 0000000000..c9006f4e71 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/LifetimePolicy.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.model; + +public enum LifetimePolicy +{ + PERMANENT, + AUTO_DELETE +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Model.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Model.java new file mode 100644 index 0000000000..45fadbdbcb --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Model.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.model; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class Model +{ + /* + * API version for the broker model + * + * 1.0 Initial version + * 1.1 Addition of mandatory virtual host type / different types of virtual host + * + */ + public static final int MODEL_MAJOR_VERSION = 1; + public static final int MODEL_MINOR_VERSION = 1; + public static final String MODEL_VERSION = MODEL_MAJOR_VERSION + "." + MODEL_MINOR_VERSION; + + private static final Model MODEL_INSTANCE = new Model(); + + private final Map, Collection>> + _parents = new HashMap, Collection>>(); + + private final Map, Collection>> + _children = new HashMap, Collection>>(); + + public static Model getInstance() + { + return MODEL_INSTANCE; + } + + private Model() + { + addRelationship(Broker.class, VirtualHost.class); + addRelationship(Broker.class, Port.class); + addRelationship(Broker.class, AccessControlProvider.class); + addRelationship(Broker.class, AuthenticationProvider.class); + addRelationship(Broker.class, GroupProvider.class); + addRelationship(Broker.class, TrustStore.class); + addRelationship(Broker.class, KeyStore.class); + addRelationship(Broker.class, Plugin.class); + + addRelationship(VirtualHost.class, Exchange.class); + addRelationship(VirtualHost.class, Queue.class); + addRelationship(VirtualHost.class, Connection.class); + addRelationship(VirtualHost.class, VirtualHostAlias.class); + + addRelationship(AuthenticationProvider.class, User.class); + addRelationship(AuthenticationProvider.class, PreferencesProvider.class); + addRelationship(User.class, GroupMember.class); + + addRelationship(GroupProvider.class, Group.class); + addRelationship(Group.class, GroupMember.class); + + addRelationship(Connection.class, Session.class); + + addRelationship(Queue.class, Binding.class); + addRelationship(Queue.class, Consumer.class); + + addRelationship(Exchange.class, Binding.class); + addRelationship(Exchange.class, Publisher.class); + + addRelationship(Session.class, Consumer.class); + addRelationship(Session.class, Publisher.class); + } + + public Collection> getParentTypes(Class child) + { + Collection> parentTypes = _parents.get(child); + return parentTypes == null ? Collections.>emptyList() + : Collections.unmodifiableCollection(parentTypes); + } + + public Collection> getChildTypes(Class parent) + { + Collection> childTypes = _children.get(parent); + return childTypes == null ? Collections.>emptyList() + : Collections.unmodifiableCollection(childTypes); + } + + private void addRelationship(Class parent, Class child) + { + Collection> parents = _parents.get(child); + if(parents == null) + { + parents = new ArrayList>(); + _parents.put(child, parents); + } + parents.add(parent); + + Collection> children = _children.get(parent); + if(children == null) + { + children = new ArrayList>(); + _children.put(parent, children); + } + children.add(child); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/PasswordCredentialManagingAuthenticationProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/PasswordCredentialManagingAuthenticationProvider.java new file mode 100644 index 0000000000..1027e5ce8c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/PasswordCredentialManagingAuthenticationProvider.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.model; + +import java.io.IOException; +import java.util.Map; + +import javax.security.auth.login.AccountNotFoundException; + +public interface PasswordCredentialManagingAuthenticationProvider extends AuthenticationProvider +{ + boolean createUser(String username, String password, Map attributes); + + void deleteUser(String user) throws AccountNotFoundException; + + void setPassword(String username, String password) throws AccountNotFoundException; + + Map> getUsers(); + + /** + * Refreshes the cache of user and password data from the underlying storage. + * + * If there is a failure whilst reloading the data, the implementation must + * throw an {@link IOException} and revert to using the previous cached username + * and password data. In this way, the broker will remain usable. + */ + void reload() throws IOException; +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Plugin.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Plugin.java new file mode 100644 index 0000000000..b9503a5841 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Plugin.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.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public interface Plugin extends ConfiguredObject +{ + //Hack, using it for the class name only for consistency with the other things. + String CREATED = "created"; + String DURABLE = "durable"; + String ID = "id"; + String LIFETIME_POLICY = "lifetimePolicy"; + String NAME = "name"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String UPDATED = "updated"; + + // Attributes + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED + )); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Port.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Port.java new file mode 100644 index 0000000000..60d62e3f27 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Port.java @@ -0,0 +1,111 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model; + +import java.security.AccessControlException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public interface Port extends ConfiguredObject +{ + String CREATED = "created"; + String DURABLE = "durable"; + String ID = "id"; + String LIFETIME_POLICY = "lifetimePolicy"; + String NAME = "name"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String UPDATED = "updated"; + String BINDING_ADDRESS = "bindingAddress"; + String PORT = "port"; + String PROTOCOLS = "protocols"; + String TRANSPORTS = "transports"; + String TCP_NO_DELAY = "tcpNoDelay"; + String SEND_BUFFER_SIZE = "sendBufferSize"; + String RECEIVE_BUFFER_SIZE = "receiveBufferSize"; + String NEED_CLIENT_AUTH = "needClientAuth"; + String WANT_CLIENT_AUTH = "wantClientAuth"; + String AUTHENTICATION_PROVIDER = "authenticationProvider"; + String KEY_STORE = "keyStore"; + String TRUST_STORES = "trustStores"; + + // Attributes + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + BINDING_ADDRESS, + PORT, + PROTOCOLS, + TRANSPORTS, + TCP_NO_DELAY, + SEND_BUFFER_SIZE, + RECEIVE_BUFFER_SIZE, + NEED_CLIENT_AUTH, + WANT_CLIENT_AUTH, + AUTHENTICATION_PROVIDER, + KEY_STORE, + TRUST_STORES + )); + + + String getBindingAddress(); + + int getPort(); + + KeyStore getKeyStore(); + + Collection getTrustStores(); + + Collection getTransports(); + + void addTransport(Transport transport) throws IllegalStateException, + AccessControlException, + IllegalArgumentException; + + Transport removeTransport(Transport transport) throws IllegalStateException, + AccessControlException, + IllegalArgumentException; + + Collection getProtocols(); + + void addProtocol(Protocol protocol) throws IllegalStateException, + AccessControlException, + IllegalArgumentException; + + Protocol removeProtocol(Protocol protocol) throws IllegalStateException, + AccessControlException, + IllegalArgumentException; + + AuthenticationProvider getAuthenticationProvider(); + + //children + Collection getVirtualHostBindings(); + Collection getConnections(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/PreferencesProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/PreferencesProvider.java new file mode 100644 index 0000000000..96d02b2ef6 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/PreferencesProvider.java @@ -0,0 +1,89 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +public interface PreferencesProvider extends ConfiguredObject +{ + String ID = "id"; + String NAME = "name"; + String TYPE = "type"; + String CREATED = "created"; + String UPDATED = "updated"; + String DURABLE = "durable"; + String LIFETIME_POLICY = "lifetimePolicy"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + + Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + STATE, + TYPE, + CREATED, + UPDATED, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE + )); + + /** + * Returns preferences {@link Map} for a given user ID + * @param userId user ID to retrieve preferences for + * @return preferences {@link Map} + */ + Map getPreferences(String userId); + + /** + * Set user preferences as specified in a given {@link Map} + * @param userId user ID to set preferences for + * @param preferences new preferences + * @return existing user preferences + */ + Map setPreferences(String userId, Map preferences); + + /** + * Delete preferences for a given user ID + * @param userId user ID to delete preferences for + * @return user preferences before the deletion + */ + Map deletePreferences(String userId); + + /** + * Returns set of the user IDs having preferences set + * @return user IDs + */ + Set listUserIDs(); + + /** + * Returns authentication provider associated with this preferences provider + * @return authentication provider + */ + AuthenticationProvider getAuthenticationProvider(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Protocol.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Protocol.java new file mode 100644 index 0000000000..e9d50fbc59 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Protocol.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.model; + +import java.util.Collection; +import java.util.EnumSet; + +import org.apache.qpid.server.protocol.AmqpProtocolVersion; + +public enum Protocol +{ + AMQP_0_8(ProtocolType.AMQP), + AMQP_0_9(ProtocolType.AMQP), + AMQP_0_9_1(ProtocolType.AMQP), + AMQP_0_10(ProtocolType.AMQP), + AMQP_1_0(ProtocolType.AMQP), + JMX_RMI(ProtocolType.JMX), + HTTP(ProtocolType.HTTP), + RMI(ProtocolType.RMI); + + private final ProtocolType _protocolType; + + private Protocol(ProtocolType type) + { + _protocolType = type; + } + + public ProtocolType getProtocolType() + { + return _protocolType; + } + + public boolean isAMQP() + { + return _protocolType == ProtocolType.AMQP; + } + + public AmqpProtocolVersion toAmqpProtocolVersion() + { + switch(this) + { + case AMQP_0_8: + return AmqpProtocolVersion.v0_8; + case AMQP_0_9: + return AmqpProtocolVersion.v0_9; + case AMQP_0_9_1: + return AmqpProtocolVersion.v0_9_1; + case AMQP_0_10: + return AmqpProtocolVersion.v0_10; + case AMQP_1_0: + return AmqpProtocolVersion.v1_0_0; + default: + throw new IllegalArgumentException(this + " is not an known AMQP protocol"); + } + } + + public static Protocol valueOfObject(Object protocolObject) + { + Protocol protocol; + if (protocolObject instanceof Protocol) + { + protocol = (Protocol) protocolObject; + } + else + { + try + { + protocol = Protocol.valueOf(String.valueOf(protocolObject)); + } + catch (Exception e) + { + throw new IllegalArgumentException("Can't convert '" + protocolObject + + "' to one of the supported protocols: " + EnumSet.allOf(Protocol.class), e); + } + } + return protocol; + } + + public static boolean hasAmqpProtocol(Collection protocols) + { + for (Protocol protocol : protocols) + { + if (protocol.isAMQP()) + { + return true; + } + } + return false; + } + + public static enum ProtocolType + { + AMQP, HTTP, JMX, RMI; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Publisher.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Publisher.java new file mode 100644 index 0000000000..cdb85d8023 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Publisher.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.model; + +public interface Publisher extends ConfiguredObject +{ +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Queue.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Queue.java new file mode 100644 index 0000000000..ae2031bd71 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Queue.java @@ -0,0 +1,155 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import org.apache.qpid.server.queue.QueueEntryVisitor; + +public interface Queue extends ConfiguredObject +{ + public static final String BINDING_COUNT = "bindingCount"; + public static final String CONSUMER_COUNT = "consumerCount"; + public static final String CONSUMER_COUNT_WITH_CREDIT = "consumerCountWithCredit"; + public static final String DISCARDS_TTL_BYTES = "discardsTtlBytes"; + public static final String DISCARDS_TTL_MESSAGES = "discardsTtlMessages"; + public static final String PERSISTENT_DEQUEUED_BYTES = "persistentDequeuedBytes"; + public static final String PERSISTENT_DEQUEUED_MESSAGES = "persistentDequeuedMessages"; + public static final String PERSISTENT_ENQUEUED_BYTES = "persistentEnqueuedBytes"; + public static final String PERSISTENT_ENQUEUED_MESSAGES = "persistentEnqueuedMessages"; + public static final String QUEUE_DEPTH_BYTES = "queueDepthBytes"; + public static final String QUEUE_DEPTH_MESSAGES = "queueDepthMessages"; + public static final String STATE_CHANGED = "stateChanged"; + public static final String TOTAL_DEQUEUED_BYTES = "totalDequeuedBytes"; + public static final String TOTAL_DEQUEUED_MESSAGES = "totalDequeuedMessages"; + public static final String TOTAL_ENQUEUED_BYTES = "totalEnqueuedBytes"; + public static final String TOTAL_ENQUEUED_MESSAGES = "totalEnqueuedMessages"; + public static final String UNACKNOWLEDGED_BYTES = "unacknowledgedBytes"; + public static final String UNACKNOWLEDGED_MESSAGES = "unacknowledgedMessages"; + + public static final Collection AVAILABLE_STATISTICS = + Collections.unmodifiableList( + Arrays.asList(BINDING_COUNT, + CONSUMER_COUNT, + CONSUMER_COUNT_WITH_CREDIT, + DISCARDS_TTL_BYTES, + DISCARDS_TTL_MESSAGES, + PERSISTENT_DEQUEUED_BYTES, + PERSISTENT_DEQUEUED_MESSAGES, + PERSISTENT_ENQUEUED_BYTES, + PERSISTENT_ENQUEUED_MESSAGES, + QUEUE_DEPTH_BYTES, + QUEUE_DEPTH_MESSAGES, + STATE_CHANGED, + TOTAL_DEQUEUED_BYTES, + TOTAL_DEQUEUED_MESSAGES, + TOTAL_ENQUEUED_BYTES, + TOTAL_ENQUEUED_MESSAGES, + UNACKNOWLEDGED_BYTES, + UNACKNOWLEDGED_MESSAGES)); + + + + public static final String ID = "id"; + public static final String DESCRIPTION = "description"; + public static final String NAME = "name"; + public static final String STATE = "state"; + public static final String DURABLE = "durable"; + public static final String LIFETIME_POLICY = "lifetimePolicy"; + public static final String TIME_TO_LIVE = "timeToLive"; + public static final String CREATED = "created"; + public static final String UPDATED = "updated"; + public static final String ARGUMENTS = "arguments"; + + public static final String ALERT_REPEAT_GAP = "alertRepeatGap"; + public static final String ALERT_THRESHOLD_MESSAGE_AGE = "alertThresholdMessageAge"; + public static final String ALERT_THRESHOLD_MESSAGE_SIZE = "alertThresholdMessageSize"; + public static final String ALERT_THRESHOLD_QUEUE_DEPTH_BYTES = "alertThresholdQueueDepthBytes"; + public static final String ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES = "alertThresholdQueueDepthMessages"; + public static final String ALTERNATE_EXCHANGE = "alternateExchange"; + public static final String EXCLUSIVE = "exclusive"; + public static final String MESSAGE_GROUP_KEY = "messageGroupKey"; + public static final String MESSAGE_GROUP_SHARED_GROUPS = "messageGroupSharedGroups"; + public static final String MESSAGE_GROUP_DEFAULT_GROUP = "messageGroupDefaultGroup"; + public static final String LVQ_KEY = "lvqKey"; + public static final String MAXIMUM_DELIVERY_ATTEMPTS = "maximumDeliveryAttempts"; + public static final String NO_LOCAL = "noLocal"; + public static final String OWNER = "owner"; + public static final String QUEUE_FLOW_CONTROL_SIZE_BYTES = "queueFlowControlSizeBytes"; + public static final String QUEUE_FLOW_RESUME_SIZE_BYTES = "queueFlowResumeSizeBytes"; + public static final String QUEUE_FLOW_STOPPED = "queueFlowStopped"; + public static final String SORT_KEY = "sortKey"; + public static final String TYPE = "type"; + public static final String PRIORITIES = "priorities"; + + public static final String CREATE_DLQ_ON_CREATION = "x-qpid-dlq-enabled"; // TODO - this value should change + + public static final String FEDERATION_EXCLUDES = "federationExcludes"; + public static final String FEDERATION_ID = "federationId"; + + + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList(ID, + NAME, + DESCRIPTION, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + TYPE, + ALTERNATE_EXCHANGE, + EXCLUSIVE, + OWNER, + NO_LOCAL, + LVQ_KEY, + SORT_KEY, + MESSAGE_GROUP_KEY, + MESSAGE_GROUP_SHARED_GROUPS, + MAXIMUM_DELIVERY_ATTEMPTS, + QUEUE_FLOW_CONTROL_SIZE_BYTES, + QUEUE_FLOW_RESUME_SIZE_BYTES, + QUEUE_FLOW_STOPPED, + ALERT_THRESHOLD_MESSAGE_AGE, + ALERT_THRESHOLD_MESSAGE_SIZE, + ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, + ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, + ALERT_REPEAT_GAP, + PRIORITIES + )); + + + //children + Collection getBindings(); + Collection getConsumers(); + + + //operations + + void visit(QueueEntryVisitor visitor); + + void delete(); + + void setNotificationListener(QueueNotificationListener listener); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/QueueNotificationListener.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/QueueNotificationListener.java new file mode 100644 index 0000000000..ab601f685c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/QueueNotificationListener.java @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model; + +import org.apache.qpid.server.queue.NotificationCheck; + +public interface QueueNotificationListener +{ + void notifyClients(NotificationCheck notification, Queue queue, String notificationMsg); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/QueueType.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/QueueType.java new file mode 100644 index 0000000000..96f2a7e2e5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/QueueType.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.model; + +public enum QueueType +{ + STANDARD, + PRIORITY, + LVQ, + SORTED +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Session.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Session.java new file mode 100644 index 0000000000..e813d0c129 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Session.java @@ -0,0 +1,82 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public interface Session extends ConfiguredObject +{ + // Statistics + + public static final String BYTES_IN = "bytesIn"; + public static final String BYTES_OUT = "bytesOut"; + public static final String CONSUMER_COUNT = "consumerCount"; + public static final String LOCAL_TRANSACTION_BEGINS = "localTransactionBegins"; + public static final String LOCAL_TRANSACTION_OPEN = "localTransactionOpen"; + public static final String LOCAL_TRANSACTION_ROLLBACKS = "localTransactionRollbacks"; + public static final String STATE_CHANGED = "stateChanged"; + public static final String UNACKNOWLEDGED_BYTES = "unacknowledgedBytes"; + public static final String UNACKNOWLEDGED_MESSAGES = "unacknowledgedMessages"; + public static final String XA_TRANSACTION_BRANCH_ENDS = "xaTransactionBranchEnds"; + public static final String XA_TRANSACTION_BRANCH_STARTS = "xaTransactionBranchStarts"; + public static final String XA_TRANSACTION_BRANCH_SUSPENDS = "xaTransactionBranchSuspends"; + + public static final Collection AVAILABLE_STATISTICS = + Collections.unmodifiableCollection(Arrays.asList(BYTES_IN, BYTES_OUT, CONSUMER_COUNT, + LOCAL_TRANSACTION_BEGINS, + LOCAL_TRANSACTION_OPEN, + LOCAL_TRANSACTION_ROLLBACKS, STATE_CHANGED, + UNACKNOWLEDGED_BYTES, UNACKNOWLEDGED_MESSAGES, + XA_TRANSACTION_BRANCH_ENDS, XA_TRANSACTION_BRANCH_STARTS, + XA_TRANSACTION_BRANCH_SUSPENDS)); + + + public static final String ID = "id"; + public static final String NAME = "name"; + public static final String STATE = "state"; + public static final String DURABLE = "durable"; + public static final String LIFETIME_POLICY = "lifetimePolicy"; + public static final String TIME_TO_LIVE = "timeToLive"; + public static final String CREATED = "created"; + public static final String UPDATED = "updated"; + + public static final String CHANNEL_ID = "channelId"; + // PRODUCER_FLOW_BLOCKED is exposed as an interim step. We will expose attribute(s) that exposing + // available credit of both producer and consumer sides. + public static final String PRODUCER_FLOW_BLOCKED = "producerFlowBlocked"; + + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableCollection(Arrays.asList(ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + CHANNEL_ID, + PRODUCER_FLOW_BLOCKED)); + + Collection getSubscriptions(); + Collection getPublishers(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/State.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/State.java new file mode 100644 index 0000000000..a4287e09b1 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/State.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model; + +public enum State +{ + INITIALISING, + QUIESCED, + STOPPED, + ACTIVE, + DELETED, + REPLICA, + ERRORED +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Statistics.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Statistics.java new file mode 100644 index 0000000000..92d6f47741 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Statistics.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.model; + +import java.util.Collection; + +public interface Statistics +{ + Collection getStatisticNames(); + public Object getStatistic(String name); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Transport.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Transport.java new file mode 100644 index 0000000000..ae6e5ac43a --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/Transport.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.model; + +import java.util.EnumSet; + +public enum Transport +{ + TCP, + SSL; + + public static Transport valueOfObject(Object transportObject) + { + Transport transport; + if (transportObject instanceof Transport) + { + transport = (Transport) transportObject; + } + else + { + try + { + transport = Transport.valueOf(String.valueOf(transportObject)); + } + catch (Exception e) + { + throw new IllegalArgumentException("Can't convert '" + transportObject + + "' to one of the supported transports: " + EnumSet.allOf(Transport.class), e); + } + } + return transport; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/TrustStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/TrustStore.java new file mode 100644 index 0000000000..d313e1832f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/TrustStore.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.model; + +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import javax.net.ssl.TrustManager; + +public interface TrustStore extends ConfiguredObject +{ + String ID = "id"; + String NAME = "name"; + String DURABLE = "durable"; + String LIFETIME_POLICY = "lifetimePolicy"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String CREATED = "created"; + String UPDATED = "updated"; + String DESCRIPTION = "description"; + + String PATH = "path"; + String PASSWORD = "password"; + String TYPE = "type"; + String PEERS_ONLY = "peersOnly"; + String TRUST_MANAGER_FACTORY_ALGORITHM = "trustManagerFactoryAlgorithm"; + + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + DESCRIPTION, + PATH, + PASSWORD, + TYPE, + PEERS_ONLY, + TRUST_MANAGER_FACTORY_ALGORITHM + )); + + public String getPassword(); + + public void setPassword(String password); + + public TrustManager[] getTrustManagers() throws GeneralSecurityException; + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/UUIDGenerator.java new file mode 100644 index 0000000000..7def89025d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/UUIDGenerator.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.model; + +import java.util.UUID; + +public class UUIDGenerator +{ + //Generates a random UUID. Used primarily by tests. + public static UUID generateRandomUUID() + { + return UUID.randomUUID(); + } + + private static UUID createUUID(String objectType, String... names) + { + StringBuilder sb = new StringBuilder(); + sb.append(objectType); + + for(String name : names) + { + sb.append("/").append(name); + } + + return UUID.nameUUIDFromBytes(sb.toString().getBytes()); + } + + public static UUID generateExchangeUUID(String exchangeName, String virtualHostName) + { + return createUUID(Exchange.class.getName(), virtualHostName, exchangeName); + } + + public static UUID generateQueueUUID(String queueName, String virtualHostName) + { + return createUUID(Queue.class.getName(), virtualHostName, queueName); + } + + public static UUID generateBindingUUID(String exchangeName, String queueName, String bindingKey, String virtualHostName) + { + return createUUID(Binding.class.getName(), virtualHostName, exchangeName, queueName, bindingKey); + } + + public static UUID generateUserUUID(String authenticationProviderName, String userName) + { + return createUUID(User.class.getName(), authenticationProviderName, userName); + } + + public static UUID generateGroupUUID(String groupProviderName, String groupName) + { + return createUUID(Group.class.getName(), groupProviderName, groupName); + } + + public static UUID generateVhostUUID(String virtualHostName) + { + return createUUID(VirtualHost.class.getName(), virtualHostName); + } + + public static UUID generateVhostAliasUUID(String virtualHostName, String portName) + { + return createUUID(VirtualHostAlias.class.getName(), virtualHostName, portName); + } + + public static UUID generateConsumerUUID(String virtualHostName, String queueName, String connectionRemoteAddress, String channelNumber, String consumerName) + { + return createUUID(Consumer.class.getName(), virtualHostName, queueName, connectionRemoteAddress, channelNumber, consumerName); + } + + public static UUID generateGroupMemberUUID(String groupProviderName, String groupName, String groupMemberName) + { + return createUUID(GroupMember.class.getName(), groupProviderName, groupName, groupMemberName); + } + + public static UUID generateBrokerChildUUID(String type, String childName) + { + return createUUID(type, childName); + } + + public static UUID generatePreferencesProviderUUID(String preferencesProviderName, String authenticationProviderName) + { + return createUUID(PreferencesProvider.class.getName(), authenticationProviderName, preferencesProviderName); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/User.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/User.java new file mode 100644 index 0000000000..f9d6f17ffd --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/User.java @@ -0,0 +1,65 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.model; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public interface User extends ConfiguredObject +{ + String CREATED = "created"; + String DURABLE = "durable"; + String ID = "id"; + String LIFETIME_POLICY = "lifetimePolicy"; + String NAME = "name"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String UPDATED = "updated"; + String PASSWORD = "password"; + + // Attributes + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + PASSWORD + )); + + public void setPassword(String password); + + public Map getPreferences(); + + public Object getPreference(String name); + + public Map setPreferences(Map preferences); + + public Map replacePreferences(Map newPreferences); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHost.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHost.java new file mode 100644 index 0000000000..ae07005679 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHost.java @@ -0,0 +1,171 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model; + +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.store.MessageStore; + +import java.security.AccessControlException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +public interface VirtualHost extends ConfiguredObject +{ + // Statistics + + public static final String BYTES_IN = "bytesIn"; + public static final String BYTES_OUT = "bytesOut"; + public static final String BYTES_RETAINED = "bytesRetained"; + public static final String LOCAL_TRANSACTION_BEGINS = "localTransactionBegins"; + public static final String LOCAL_TRANSACTION_ROLLBACKS = "localTransactionRollbacks"; + public static final String MESSAGES_IN = "messagesIn"; + public static final String MESSAGES_OUT = "messagesOut"; + public static final String MESSAGES_RETAINED = "messagesRetained"; + public static final String STATE_CHANGED = "stateChanged"; + public static final String XA_TRANSACTION_BRANCH_ENDS = "xaTransactionBranchEnds"; + public static final String XA_TRANSACTION_BRANCH_STARTS = "xaTransactionBranchStarts"; + public static final String XA_TRANSACTION_BRANCH_SUSPENDS = "xaTransactionBranchSuspends"; + public static final String QUEUE_COUNT = "queueCount"; + public static final String EXCHANGE_COUNT = "exchangeCount"; + public static final String CONNECTION_COUNT = "connectionCount"; + + public static final Collection AVAILABLE_STATISTICS = + Collections.unmodifiableList( + Arrays.asList(BYTES_IN, BYTES_OUT, BYTES_RETAINED, LOCAL_TRANSACTION_BEGINS, + LOCAL_TRANSACTION_ROLLBACKS, MESSAGES_IN, MESSAGES_OUT, MESSAGES_RETAINED, STATE_CHANGED, + XA_TRANSACTION_BRANCH_ENDS, XA_TRANSACTION_BRANCH_STARTS, XA_TRANSACTION_BRANCH_SUSPENDS, + QUEUE_COUNT, EXCHANGE_COUNT, CONNECTION_COUNT)); + + String QUEUE_ALERT_REPEAT_GAP = "queue.alertRepeatGap"; + String QUEUE_ALERT_THRESHOLD_MESSAGE_AGE = "queue.alertThresholdMessageAge"; + String QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE = "queue.alertThresholdMessageSize"; + String QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES = "queue.alertThresholdQueueDepthBytes"; + String QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES = "queue.alertThresholdQueueDepthMessages"; + String QUEUE_DEAD_LETTER_QUEUE_ENABLED = "queue.deadLetterQueueEnabled"; + String QUEUE_MAXIMUM_DELIVERY_ATTEMPTS = "queue.maximumDeliveryAttempts"; + String QUEUE_FLOW_CONTROL_SIZE_BYTES = "queue.flowControlSizeBytes"; + String QUEUE_FLOW_RESUME_SIZE_BYTES = "queue.flowResumeSizeBytes"; + + String HOUSEKEEPING_CHECK_PERIOD = "housekeepingCheckPeriod"; + String STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE = "storeTransactionIdleTimeoutClose"; + String STORE_TRANSACTION_IDLE_TIMEOUT_WARN = "storeTransactionIdleTimeoutWarn"; + String STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE = "storeTransactionOpenTimeoutClose"; + String STORE_TRANSACTION_OPEN_TIMEOUT_WARN = "storeTransactionOpenTimeoutWarn"; + String STORE_TYPE = "storeType"; + String STORE_PATH = "storePath"; + String CONFIG_STORE_TYPE = "configStoreType"; + String CONFIG_STORE_PATH = "configStorePath"; + String SUPPORTED_EXCHANGE_TYPES = "supportedExchangeTypes"; + String SUPPORTED_QUEUE_TYPES = "supportedQueueTypes"; + String CREATED = "created"; + String DURABLE = "durable"; + String ID = "id"; + String LIFETIME_POLICY = "lifetimePolicy"; + String NAME = "name"; + String STATE = "state"; + String TIME_TO_LIVE = "timeToLive"; + String TYPE = "type"; + String UPDATED = "updated"; + String CONFIG_PATH = "configPath"; + + // Attributes + public static final Collection AVAILABLE_ATTRIBUTES = + Collections.unmodifiableList( + Arrays.asList( + ID, + NAME, + TYPE, + STATE, + DURABLE, + LIFETIME_POLICY, + TIME_TO_LIVE, + CREATED, + UPDATED, + SUPPORTED_EXCHANGE_TYPES, + SUPPORTED_QUEUE_TYPES, + QUEUE_DEAD_LETTER_QUEUE_ENABLED, + HOUSEKEEPING_CHECK_PERIOD, + QUEUE_MAXIMUM_DELIVERY_ATTEMPTS, + QUEUE_FLOW_CONTROL_SIZE_BYTES, + QUEUE_FLOW_RESUME_SIZE_BYTES, + CONFIG_STORE_TYPE, + CONFIG_STORE_PATH, + STORE_TYPE, + STORE_PATH, + STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE, + STORE_TRANSACTION_IDLE_TIMEOUT_WARN, + STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE, + STORE_TRANSACTION_OPEN_TIMEOUT_WARN, + QUEUE_ALERT_REPEAT_GAP, + QUEUE_ALERT_THRESHOLD_MESSAGE_AGE, + QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE, + QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, + QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, + CONFIG_PATH)); + + int CURRENT_CONFIG_VERSION = 3; + + //children + Collection getAliases(); + Collection getConnections(); + Collection getQueues(); + Collection getExchanges(); + + Exchange createExchange(String name, State initialState, boolean durable, + LifetimePolicy lifetime, long ttl, String type, Map attributes) + throws AccessControlException, IllegalArgumentException; + + Queue createQueue(String name, State initialState, boolean durable, + boolean exclusive, LifetimePolicy lifetime, long ttl, Map attributes) + throws AccessControlException, IllegalArgumentException; + + Collection getExchangeTypes(); + + public static interface Transaction + { + void dequeue(QueueEntry entry); + + void copy(QueueEntry entry, Queue queue); + + void move(QueueEntry entry, Queue queue); + + } + + public static interface TransactionalOperation + { + void withinTransaction(Transaction txn); + } + + void executeTransaction(TransactionalOperation op); + + /** + * A temporary hack to expose host security manager. + * TODO We need to add and implement an authorization provider configured object instead + */ + SecurityManager getSecurityManager(); + + MessageStore getMessageStore(); + + String getType(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHostAlias.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHostAlias.java new file mode 100644 index 0000000000..31403d78e5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHostAlias.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.model; + +import java.security.AccessControlException; +import java.util.Collection; + +public interface VirtualHostAlias extends ConfiguredObject +{ + // parents + Port getPort(); + VirtualHost getVirtualHost(); + + // children + Collection getAuthenticationMethods(); + + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java new file mode 100644 index 0000000000..9ac2cb00a3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractAdapter.java @@ -0,0 +1,477 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.security.AccessControlException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.model.ConfigurationChangeListener; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.updater.ChangeAttributesTask; +import org.apache.qpid.server.configuration.updater.ChangeStateTask; +import org.apache.qpid.server.configuration.updater.CreateChildTask; +import org.apache.qpid.server.configuration.updater.SetAttributeTask; +import org.apache.qpid.server.configuration.updater.TaskExecutor; + +public abstract class AbstractAdapter implements ConfiguredObject +{ + private static final Object ID = "id"; + private final Map _attributes = new HashMap(); + private final Map, ConfiguredObject> _parents = + new HashMap, ConfiguredObject>(); + private final Collection _changeListeners = + new ArrayList(); + + private final UUID _id; + private final Map _defaultAttributes = new HashMap(); + private final TaskExecutor _taskExecutor; + + protected AbstractAdapter(UUID id, Map defaults, Map attributes, TaskExecutor taskExecutor) + { + this(id, defaults, attributes, taskExecutor, true); + } + + protected AbstractAdapter(UUID id, Map defaults, Map attributes, + TaskExecutor taskExecutor, boolean filterAttributes) + + { + _taskExecutor = taskExecutor; + _id = id; + if (attributes != null) + { + Collection names = getAttributeNames(); + if(filterAttributes) + { + for (String name : names) + { + if (attributes.containsKey(name)) + { + final Object value = attributes.get(name); + if(value != null) + { + _attributes.put(name, value); + } + } + } + } + else + { + for(Map.Entry entry : attributes.entrySet()) + { + if(entry.getValue()!=null) + { + _attributes.put(entry.getKey(),entry.getValue()); + } + } + } + } + if (defaults != null) + { + _defaultAttributes.putAll(defaults); + } + } + + protected AbstractAdapter(UUID id, TaskExecutor taskExecutor) + { + this(id, null, null, taskExecutor); + } + + public final UUID getId() + { + return _id; + } + + public State getDesiredState() + { + return null; //TODO + } + + @Override + public final State setDesiredState(final State currentState, final State desiredState) + throws IllegalStateTransitionException, AccessControlException + { + if (_taskExecutor.isTaskExecutorThread()) + { + authoriseSetDesiredState(currentState, desiredState); + if (setState(currentState, desiredState)) + { + notifyStateChanged(currentState, desiredState); + return desiredState; + } + else + { + return getActualState(); + } + } + else + { + return (State)_taskExecutor.submitAndWait(new ChangeStateTask(this, currentState, desiredState)); + } + } + + /** + * @return true when the state has been successfully updated to desiredState or false otherwise + */ + protected abstract boolean setState(State currentState, State desiredState); + + protected void notifyStateChanged(final State currentState, final State desiredState) + { + synchronized (_changeListeners) + { + List copy = new ArrayList(_changeListeners); + for(ConfigurationChangeListener listener : copy) + { + listener.stateChanged(this, currentState, desiredState); + } + } + } + + public void addChangeListener(final ConfigurationChangeListener listener) + { + if(listener == null) + { + throw new NullPointerException("Cannot add a null listener"); + } + synchronized (_changeListeners) + { + if(!_changeListeners.contains(listener)) + { + _changeListeners.add(listener); + } + } + } + + public boolean removeChangeListener(final ConfigurationChangeListener listener) + { + if(listener == null) + { + throw new NullPointerException("Cannot remove a null listener"); + } + synchronized (_changeListeners) + { + return _changeListeners.remove(listener); + } + } + + protected void childAdded(ConfiguredObject child) + { + synchronized (_changeListeners) + { + List copy = new ArrayList(_changeListeners); + for(ConfigurationChangeListener listener : copy) + { + listener.childAdded(this, child); + } + } + } + + protected void childRemoved(ConfiguredObject child) + { + synchronized (_changeListeners) + { + List copy = new ArrayList(_changeListeners); + for(ConfigurationChangeListener listener : copy) + { + listener.childRemoved(this, child); + } + } + } + + protected void attributeSet(String attrinuteName, Object oldAttributeValue, Object newAttributeValue) + { + synchronized (_changeListeners) + { + List copy = new ArrayList(_changeListeners); + for(ConfigurationChangeListener listener : copy) + { + listener.attributeSet(this, attrinuteName, oldAttributeValue, newAttributeValue); + } + } + } + + private final Object getDefaultAttribute(String name) + { + return _defaultAttributes.get(name); + } + + @Override + public Object getAttribute(String name) + { + Object value = getActualAttribute(name); + if (value == null) + { + value = getDefaultAttribute(name); + } + return value; + } + + @Override + public final Map getActualAttributes() + { + synchronized (_attributes) + { + return new HashMap(_attributes); + } + } + + private Object getActualAttribute(final String name) + { + synchronized (_attributes) + { + return _attributes.get(name); + } + } + + public Object setAttribute(final String name, final Object expected, final Object desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + if (_taskExecutor.isTaskExecutorThread()) + { + authoriseSetAttribute(name, expected, desired); + if (changeAttribute(name, expected, desired)) + { + attributeSet(name, expected, desired); + return desired; + } + else + { + return getAttribute(name); + } + } + else + { + return _taskExecutor.submitAndWait(new SetAttributeTask(this, name, expected, desired)); + } + } + + protected boolean changeAttribute(final String name, final Object expected, final Object desired) + { + synchronized (_attributes) + { + Object currentValue = getAttribute(name); + if((currentValue == null && expected == null) + || (currentValue != null && currentValue.equals(expected))) + { + //TODO: dont put nulls + _attributes.put(name, desired); + return true; + } + else + { + return false; + } + } + } + + public T getParent(final Class clazz) + { + synchronized (_parents) + { + return (T) _parents.get(clazz); + } + } + + protected void addParent(Class clazz, T parent) + { + synchronized (_parents) + { + _parents.put(clazz, parent); + } + } + + protected void removeParent(Class clazz) + { + synchronized (this) + { + _parents.remove(clazz); + } + } + + public Collection getAttributeNames() + { + synchronized(_attributes) + { + return new ArrayList(_attributes.keySet()); + } + } + + @Override + public String toString() + { + return getClass().getSimpleName() + " [id=" + _id + ", name=" + getName() + "]"; + } + + @SuppressWarnings("unchecked") + @Override + public C createChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + if (_taskExecutor.isTaskExecutorThread()) + { + authoriseCreateChild(childClass, attributes, otherParents); + C child = addChild(childClass, attributes, otherParents); + if (child != null) + { + childAdded(child); + } + return child; + } + else + { + return (C)_taskExecutor.submitAndWait(new CreateChildTask(this, childClass, attributes, otherParents)); + } + } + + protected C addChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + throw new UnsupportedOperationException(); + } + + + protected TaskExecutor getTaskExecutor() + { + return _taskExecutor; + } + + @Override + public void setAttributes(final Map attributes) throws IllegalStateException, AccessControlException, IllegalArgumentException + { + if (getTaskExecutor().isTaskExecutorThread()) + { + authoriseSetAttributes(attributes); + changeAttributes(attributes); + } + else + { + getTaskExecutor().submitAndWait(new ChangeAttributesTask(this, attributes)); + } + } + + protected void changeAttributes(final Map attributes) + { + validateChangeAttributes(attributes); + Collection names = getAttributeNames(); + for (String name : names) + { + if (attributes.containsKey(name)) + { + Object desired = attributes.get(name); + Object expected = getAttribute(name); + if (changeAttribute(name, expected, desired)) + { + attributeSet(name, expected, desired); + } + } + } + } + + protected void validateChangeAttributes(final Map attributes) + { + if (attributes.containsKey(ID)) + { + UUID id = getId(); + Object idAttributeValue = attributes.get(ID); + if (idAttributeValue != null && !idAttributeValue.equals(id.toString())) + { + throw new IllegalConfigurationException("Cannot change existing configured object id"); + } + } + } + + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + // allowed by default + } + + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + // allowed by default + } + + protected void authoriseCreateChild(Class childClass, Map attributes, ConfiguredObject... otherParents) throws AccessControlException + { + // allowed by default + } + + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + // allowed by default + } + + protected Map getDefaultAttributes() + { + return _defaultAttributes; + } + + /** + * Returns a map of effective attribute values that would result + * if applying the supplied changes. Does not apply the changes. + */ + protected Map generateEffectiveAttributes(Map changedValues) + { + //Build a new set of effective attributes that would be + //the result of applying the attribute changes, so we + //can validate the configuration that would result + + Map defaultValues = getDefaultAttributes(); + Map existingActualValues = getActualAttributes(); + + //create a new merged map, starting with the defaults + Map merged = new HashMap(defaultValues); + + for(String name : getAttributeNames()) + { + if(changedValues.containsKey(name)) + { + Object changedValue = changedValues.get(name); + if(changedValue != null) + { + //use the new non-null value for the merged values + merged.put(name, changedValue); + } + else + { + //we just use the default (if there was one) since the changed + //value is null and effectively clears any existing actual value + } + } + else if(existingActualValues.get(name) != null) + { + //Use existing non-null actual value for the merge + merged.put(name, existingActualValues.get(name)); + } + else + { + //There was neither a change or an existing non-null actual + //value, so just use the default value (if there was one). + } + } + + return merged; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.java new file mode 100644 index 0000000000..707cf8076d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractKeyStoreAdapter.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.server.model.adapter; + +import java.security.AccessControlException; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.util.MapValueConverter; + +public abstract class AbstractKeyStoreAdapter extends AbstractAdapter +{ + public static final String DUMMY_PASSWORD_MASK = "********"; + public static final String DEFAULT_KEYSTORE_TYPE = java.security.KeyStore.getDefaultType(); + + private String _name; + private String _password; + + protected AbstractKeyStoreAdapter(UUID id, Broker broker, Map defaults, + Map attributes) + { + super(id, defaults, attributes, broker.getTaskExecutor()); + addParent(Broker.class, broker); + + _name = MapValueConverter.getStringAttribute(TrustStore.NAME, attributes); + _password = MapValueConverter.getStringAttribute(TrustStore.PASSWORD, attributes); + MapValueConverter.assertMandatoryAttribute(KeyStore.PATH, attributes); + } + + @Override + public String getName() + { + return _name; + } + + @Override + public String setName(String currentName, String desiredName) throws IllegalStateException, AccessControlException + { + throw new IllegalStateException(); + } + + @Override + public State getActualState() + { + return State.ACTIVE; + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, LifetimePolicy desired) throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new IllegalStateException(); + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public Collection getChildren(Class clazz) + { + return Collections.emptySet(); + } + + @Override + public C createChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + throw new UnsupportedOperationException(); + } + + @Override + public Object getAttribute(String name) + { + if(KeyStore.ID.equals(name)) + { + return getId(); + } + else if(KeyStore.NAME.equals(name)) + { + return getName(); + } + else if(KeyStore.STATE.equals(name)) + { + return getActualState(); + } + else if(KeyStore.DURABLE.equals(name)) + { + return isDurable(); + } + else if(KeyStore.LIFETIME_POLICY.equals(name)) + { + return getLifetimePolicy(); + } + else if(KeyStore.TIME_TO_LIVE.equals(name)) + { + return getTimeToLive(); + } + else if(KeyStore.CREATED.equals(name)) + { + + } + else if(KeyStore.UPDATED.equals(name)) + { + + } + else if(KeyStore.PASSWORD.equals(name)) + { + // For security reasons we don't expose the password + if (getPassword() != null) + { + return DUMMY_PASSWORD_MASK; + } + + return null; + } + + return super.getAttribute(name); + } + + public String getPassword() + { + return _password; + } + + public void setPassword(String password) + { + _password = password; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractPluginAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractPluginAdapter.java new file mode 100644 index 0000000000..2867a92410 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AbstractPluginAdapter.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.model.adapter; + +import java.security.AccessControlException; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Plugin; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.security.access.Operation; + +public abstract class AbstractPluginAdapter extends AbstractAdapter implements Plugin +{ + private Broker _broker; + + protected AbstractPluginAdapter(UUID id, Map defaults, Map attributes, Broker broker) + { + super(id, defaults, attributes, broker.getTaskExecutor()); + _broker = broker; + addParent(Broker.class, broker); + } + + @Override + public String setName(String currentName, String desiredName) throws IllegalStateException, AccessControlException + { + throw new UnsupportedOperationException(); + } + + @Override + public State getActualState() + { + return null; + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, LifetimePolicy desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public Statistics getStatistics() + { + return null; + } + + @Override + public Collection getChildren(Class clazz) + { + return Collections.emptyList(); + } + + @Override + public Collection getAttributeNames() + { + return AVAILABLE_ATTRIBUTES; + } + + @Override + public Object getAttribute(String name) + { + if (ID.equals(name)) + { + return getId(); + } + else if (STATE.equals(name)) + { + return getActualState(); + } + else if (DURABLE.equals(name)) + { + return isDurable(); + } + else if (LIFETIME_POLICY.equals(name)) + { + return getLifetimePolicy(); + } + else if (TIME_TO_LIVE.equals(name)) + { + return getTimeToLive(); + } + else if (CREATED.equals(name)) + { + + } + else if (UPDATED.equals(name)) + { + + } + return super.getAttribute(name); + } + + @Override + public C createChild(Class childClass, Map attributes, + ConfiguredObject... otherParents) + { + throw new UnsupportedOperationException(); + } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), Plugin.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of plugin is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), Plugin.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of plugin attribute is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), Plugin.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of plugin attributes is denied"); + } + } + + protected Broker getBroker() + { + return _broker; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderAdapter.java new file mode 100644 index 0000000000..a6fe191523 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderAdapter.java @@ -0,0 +1,300 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.security.AccessControlException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.model.AccessControlProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.plugin.AccessControlFactory; +import org.apache.qpid.server.security.AccessControl; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.util.MapValueConverter; + +public class AccessControlProviderAdapter extends AbstractAdapter implements AccessControlProvider +{ + private static final Logger LOGGER = Logger.getLogger(AccessControlProviderAdapter.class); + + protected AccessControl _accessControl; + protected final Broker _broker; + + protected Collection _supportedAttributes; + protected Map _factories; + private AtomicReference _state; + + public AccessControlProviderAdapter(UUID id, Broker broker, AccessControl accessControl, Map attributes, Collection attributeNames) + { + super(id, null, null, broker.getTaskExecutor()); + + if (accessControl == null) + { + throw new IllegalArgumentException("AccessControl must not be null"); + } + + _accessControl = accessControl; + _broker = broker; + _supportedAttributes = createSupportedAttributes(attributeNames); + addParent(Broker.class, broker); + + State state = MapValueConverter.getEnumAttribute(State.class, STATE, attributes, State.INITIALISING); + _state = new AtomicReference(state); + + // set attributes now after all attribute names are known + if (attributes != null) + { + for (String name : _supportedAttributes) + { + if (attributes.containsKey(name)) + { + changeAttribute(name, null, attributes.get(name)); + } + } + } + } + + protected Collection createSupportedAttributes(Collection factoryAttributes) + { + List attributesNames = new ArrayList(AVAILABLE_ATTRIBUTES); + if (factoryAttributes != null) + { + attributesNames.addAll(factoryAttributes); + } + + return Collections.unmodifiableCollection(attributesNames); + } + + @Override + public String getName() + { + return (String)getAttribute(AccessControlProvider.NAME); + } + + @Override + public String setName(String currentName, String desiredName) throws IllegalStateException, AccessControlException + { + return null; + } + + @Override + public State getActualState() + { + return _state.get(); + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return null; + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return 0; + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public Collection getAttributeNames() + { + return _supportedAttributes; + } + + @Override + public Object getAttribute(String name) + { + if(CREATED.equals(name)) + { + // TODO + } + else if(DURABLE.equals(name)) + { + return true; + } + else if(ID.equals(name)) + { + return getId(); + } + else if(LIFETIME_POLICY.equals(name)) + { + return LifetimePolicy.PERMANENT; + } + else if(STATE.equals(name)) + { + return getActualState(); + } + else if(TIME_TO_LIVE.equals(name)) + { + // TODO + } + else if(UPDATED.equals(name)) + { + // TODO + } + return super.getAttribute(name); + } + + @Override + public Collection getChildren(Class clazz) + { + return Collections.emptySet(); + } + + @Override + public boolean setState(State currentState, State desiredState) + throws IllegalStateTransitionException, AccessControlException + { + State state = _state.get(); + + if(desiredState == State.DELETED) + { + return _state.compareAndSet(state, State.DELETED); + } + else if (desiredState == State.QUIESCED) + { + return _state.compareAndSet(state, State.QUIESCED); + } + else if(desiredState == State.ACTIVE) + { + if ((state == State.INITIALISING || state == State.QUIESCED) && _state.compareAndSet(state, State.ACTIVE)) + { + try + { + _accessControl.open(); + return true; + } + catch(RuntimeException e) + { + _state.compareAndSet(State.ACTIVE, State.ERRORED); + if (_broker.isManagementMode()) + { + LOGGER.warn("Failed to activate ACL provider: " + getName(), e); + } + else + { + throw e; + } + } + } + else + { + throw new IllegalStateException("Can't activate access control provider in " + state + " state"); + } + } + else if(desiredState == State.STOPPED) + { + if(_state.compareAndSet(state, State.STOPPED)) + { + _accessControl.close(); + return true; + } + + return false; + } + return false; + } + + + @Override + protected void changeAttributes(Map attributes) + { + throw new UnsupportedOperationException("Changing attributes on AccessControlProvider is not supported"); + } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), AccessControlProvider.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of AccessControlProvider is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), AccessControlProvider.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of AccessControlProvider attributes is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), AccessControlProvider.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of AccessControlProvider attributes is denied"); + } + } + + public AccessControl getAccessControl() + { + return _accessControl; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderFactory.java new file mode 100644 index 0000000000..6cdf2f2c1a --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AccessControlProviderFactory.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.model.adapter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.model.AccessControlProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.plugin.AccessControlFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.security.AccessControl; + +public class AccessControlProviderFactory +{ + private final Iterable _factories; + private Collection _supportedAcessControlProviders; + + public AccessControlProviderFactory(QpidServiceLoader accessControlFactoryServiceLoader) + { + _factories = accessControlFactoryServiceLoader.instancesOf(AccessControlFactory.class); + List supportedAccessControlProviders = new ArrayList(); + for (AccessControlFactory factory : _factories) + { + supportedAccessControlProviders.add(factory.getType()); + } + _supportedAcessControlProviders = Collections.unmodifiableCollection(supportedAccessControlProviders); + } + + /** + * Creates {@link AccessControlProvider} for given ID, {@link Broker} and attributes. + *

+ * The configured {@link AccessControlFactory}'s are used to try to create the {@link AccessControlProvider}. + * The first non-null instance is returned. The factories are used in non-deterministic order. + */ + public AccessControlProvider create(UUID id, Broker broker, Map attributes) + { + AccessControlProvider ac = createAccessControlProvider(id, broker, attributes); + ac.getAccessControl().onCreate(); + + return ac; + } + + public AccessControlProvider recover(UUID id, Broker broker, Map attributes) + { + return createAccessControlProvider(id, broker, attributes); + } + + private AccessControlProvider createAccessControlProvider(UUID id, + Broker broker, Map attributes) + { + for (AccessControlFactory factory : _factories) + { + AccessControl accessControl = factory.createInstance(attributes); + if (accessControl != null) + { + return new AccessControlProviderAdapter(id, broker,accessControl, attributes, factory.getAttributeNames()); + } + } + + throw new IllegalArgumentException("No access control provider factory found for configuration attributes " + attributes); + } + + public Collection getSupportedAuthenticationProviders() + { + return _supportedAcessControlProviders; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.java new file mode 100644 index 0000000000..a4ce95e5aa --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AmqpPortAdapter.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.model.adapter; + +import static org.apache.qpid.transport.ConnectionSettings.WILDCARD_ADDRESS; + +import java.net.InetSocketAddress; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.BrokerMessages; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Protocol; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.protocol.AmqpProtocolVersion; +import org.apache.qpid.server.protocol.MultiVersionProtocolEngineFactory; +import org.apache.qpid.transport.NetworkTransportConfiguration; +import org.apache.qpid.transport.network.IncomingNetworkTransport; +import org.apache.qpid.transport.network.security.ssl.QpidMultipleTrustManager; + +public class AmqpPortAdapter extends PortAdapter +{ + private final Broker _broker; + private IncomingNetworkTransport _transport; + + public AmqpPortAdapter(UUID id, Broker broker, Map attributes, Map defaultAttributes, TaskExecutor taskExecutor) + { + super(id, broker, attributes, defaultAttributes, taskExecutor); + _broker = broker; + } + + @Override + protected void onActivate() + { + Collection transports = getTransports(); + Set supported = convertFromModelProtocolsToAmqp(getProtocols()); + + SSLContext sslContext = null; + if (transports.contains(Transport.SSL)) + { + sslContext = createSslContext(); + } + + AmqpProtocolVersion defaultSupportedProtocolReply = getDefaultAmqpSupportedReply(); + + String bindingAddress = (String) getAttribute(Port.BINDING_ADDRESS); + if (WILDCARD_ADDRESS.equals(bindingAddress)) + { + bindingAddress = null; + } + Integer port = (Integer) getAttribute(Port.PORT); + InetSocketAddress bindingSocketAddress = null; + if ( bindingAddress == null ) + { + bindingSocketAddress = new InetSocketAddress(port); + } + else + { + bindingSocketAddress = new InetSocketAddress(bindingAddress, port); + } + + final NetworkTransportConfiguration settings = new ServerNetworkTransportConfiguration( + bindingSocketAddress, (Boolean)getAttribute(TCP_NO_DELAY), + (Integer)getAttribute(SEND_BUFFER_SIZE), (Integer)getAttribute(RECEIVE_BUFFER_SIZE), + (Boolean)getAttribute(NEED_CLIENT_AUTH), (Boolean)getAttribute(WANT_CLIENT_AUTH)); + + _transport = org.apache.qpid.transport.network.Transport.getIncomingTransportInstance(); + final MultiVersionProtocolEngineFactory protocolEngineFactory = new MultiVersionProtocolEngineFactory( + _broker, transports.contains(Transport.TCP) ? sslContext : null, + settings.wantClientAuth(), settings.needClientAuth(), + supported, defaultSupportedProtocolReply, this, transports.contains(Transport.TCP) ? Transport.TCP : Transport.SSL); + + _transport.accept(settings, protocolEngineFactory, transports.contains(Transport.TCP) ? null : sslContext); + for(Transport transport : getTransports()) + { + CurrentActor.get().message(BrokerMessages.LISTENING(String.valueOf(transport), getPort())); + } + } + + @Override + protected void onStop() + { + if (_transport != null) + { + for(Transport transport : getTransports()) + { + CurrentActor.get().message(BrokerMessages.SHUTTING_DOWN(String.valueOf(transport), getPort())); + } + _transport.close(); + } + } + + private Set convertFromModelProtocolsToAmqp(Collection modelProtocols) + { + Set amqpProtocols = new HashSet(); + for (Protocol protocol : modelProtocols) + { + amqpProtocols.add(protocol.toAmqpProtocolVersion()); + } + return amqpProtocols; + } + + private SSLContext createSslContext() + { + KeyStore keyStore = getKeyStore(); + Collection trustStores = getTrustStores(); + + boolean needClientCert = (Boolean)getAttribute(NEED_CLIENT_AUTH) || (Boolean)getAttribute(WANT_CLIENT_AUTH); + if (needClientCert && trustStores.isEmpty()) + { + throw new IllegalConfigurationException("Client certificate authentication is enabled on AMQP port '" + + this.getName() + "' but no trust store defined"); + } + + try + { + SSLContext sslContext = SSLContext.getInstance("TLS"); + KeyManager[] keyManagers = keyStore.getKeyManagers(); + + TrustManager[] trustManagers; + if(trustStores == null || trustStores.isEmpty()) + { + trustManagers = null; + } + else if(trustStores.size() == 1) + { + trustManagers = trustStores.iterator().next().getTrustManagers(); + } + else + { + Collection trustManagerList = new ArrayList(); + final QpidMultipleTrustManager mulTrustManager = new QpidMultipleTrustManager(); + + for(TrustStore ts : trustStores) + { + TrustManager[] managers = ts.getTrustManagers(); + if(managers != null) + { + for(TrustManager manager : managers) + { + if(manager instanceof X509TrustManager) + { + mulTrustManager.addTrustManager((X509TrustManager)manager); + } + else + { + trustManagerList.add(manager); + } + } + } + } + if(!mulTrustManager.isEmpty()) + { + trustManagerList.add(mulTrustManager); + } + trustManagers = trustManagerList.toArray(new TrustManager[trustManagerList.size()]); + } + sslContext.init(keyManagers, trustManagers, null); + + return sslContext; + + } + catch (GeneralSecurityException e) + { + throw new RuntimeException("Unable to create SSLContext for key or trust store", e); + } + } + + private AmqpProtocolVersion getDefaultAmqpSupportedReply() + { + String defaultAmqpSupportedReply = System.getProperty(BrokerProperties.PROPERTY_DEFAULT_SUPPORTED_PROTOCOL_REPLY); + if (defaultAmqpSupportedReply != null) + { + return AmqpProtocolVersion.valueOf(defaultAmqpSupportedReply); + } + return null; + } + + class ServerNetworkTransportConfiguration implements NetworkTransportConfiguration + { + private final InetSocketAddress _bindingSocketAddress; + private final Boolean _tcpNoDelay; + private final Integer _sendBufferSize; + private final Integer _receiveBufferSize; + private final boolean _needClientAuth; + private final boolean _wantClientAuth; + + public ServerNetworkTransportConfiguration( + InetSocketAddress bindingSocketAddress, boolean tcpNoDelay, + int sendBufferSize, int receiveBufferSize, + boolean needClientAuth, boolean wantClientAuth) + { + _bindingSocketAddress = bindingSocketAddress; + _tcpNoDelay = tcpNoDelay; + _sendBufferSize = sendBufferSize; + _receiveBufferSize = receiveBufferSize; + _needClientAuth = needClientAuth; + _wantClientAuth = wantClientAuth; + } + + @Override + public boolean wantClientAuth() + { + return _wantClientAuth; + } + + @Override + public boolean needClientAuth() + { + return _needClientAuth; + } + + @Override + public Boolean getTcpNoDelay() + { + return _tcpNoDelay; + } + + @Override + public Integer getSendBufferSize() + { + return _sendBufferSize; + } + + @Override + public Integer getReceiveBufferSize() + { + return _receiveBufferSize; + } + + @Override + public InetSocketAddress getAddress() + { + return _bindingSocketAddress; + } + }; + + public String toString() + { + return getName(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java new file mode 100644 index 0000000000..cbf8e1ba30 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderAdapter.java @@ -0,0 +1,826 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.io.IOException; +import java.security.AccessControlException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.IntegrityViolationException; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.PasswordCredentialManagingAuthenticationProvider; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.model.User; +import org.apache.qpid.server.model.VirtualHostAlias; +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManagerFactory; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.util.MapValueConverter; + +public abstract class AuthenticationProviderAdapter extends AbstractAdapter implements AuthenticationProvider +{ + private static final Logger LOGGER = Logger.getLogger(AuthenticationProviderAdapter.class); + + protected T _authManager; + protected final Broker _broker; + + protected Collection _supportedAttributes; + protected Map _factories; + private final AtomicReference _state; + private PreferencesProviderCreator _preferencesProviderCreator; + private PreferencesProvider _preferencesProvider; + + private AuthenticationProviderAdapter(UUID id, Broker broker, final T authManager, Map attributes, Collection attributeNames, PreferencesProviderCreator preferencesProviderCreator) + { + super(id, null, null, broker.getTaskExecutor()); + _authManager = authManager; + _broker = broker; + _supportedAttributes = createSupportedAttributes(attributeNames); + _factories = getAuthenticationManagerFactories(); + + State state = MapValueConverter.getEnumAttribute(State.class, STATE, attributes, State.INITIALISING); + _state = new AtomicReference(state); + addParent(Broker.class, broker); + + _preferencesProviderCreator = preferencesProviderCreator; + + // set attributes now after all attribute names are known + if (attributes != null) + { + for (String name : _supportedAttributes) + { + if (attributes.containsKey(name)) + { + changeAttribute(name, null, attributes.get(name)); + } + } + } + } + + T getAuthManager() + { + return _authManager; + } + + @Override + public Collection getVirtualHostPortBindings() + { + return Collections.emptyList(); + } + + @Override + public String getName() + { + return (String)getAttribute(AuthenticationProvider.NAME); + } + + @Override + public String setName(String currentName, String desiredName) throws IllegalStateException, AccessControlException + { + return null; + } + + @Override + public State getActualState() + { + return _state.get(); + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return null; + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return 0; + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public Collection getAttributeNames() + { + return _supportedAttributes; + } + + @Override + public Object getAttribute(String name) + { + if(CREATED.equals(name)) + { + // TODO + } + else if(DURABLE.equals(name)) + { + return true; + } + else if(ID.equals(name)) + { + return getId(); + } + else if(LIFETIME_POLICY.equals(name)) + { + return LifetimePolicy.PERMANENT; + } + else if(STATE.equals(name)) + { + return getActualState(); + } + else if(TIME_TO_LIVE.equals(name)) + { + // TODO + } + else if(UPDATED.equals(name)) + { + // TODO + } + return super.getAttribute(name); + } + + @SuppressWarnings("unchecked") + @Override + public Collection getChildren(Class clazz) + { + if (clazz == PreferencesProvider.class && _preferencesProvider != null) + { + return (Collection)Collections.singleton(_preferencesProvider); + } + return Collections.emptySet(); + } + + @Override + public boolean setState(State currentState, State desiredState) + throws IllegalStateTransitionException, AccessControlException + { + State state = _state.get(); + if(desiredState == State.DELETED) + { + String providerName = getName(); + + // verify that provider is not in use + Collection ports = new ArrayList(_broker.getPorts()); + for (Port port : ports) + { + if (providerName.equals(port.getAttribute(Port.AUTHENTICATION_PROVIDER))) + { + throw new IntegrityViolationException("Authentication provider '" + providerName + "' is set on port " + port.getName()); + } + } + + if ((state == State.INITIALISING || state == State.ACTIVE || state == State.STOPPED || state == State.QUIESCED || state == State.ERRORED) + && _state.compareAndSet(state, State.DELETED)) + { + _authManager.close(); + _authManager.onDelete(); + if (_preferencesProvider != null) + { + _preferencesProvider.setDesiredState(_preferencesProvider.getActualState(), State.DELETED); + } + return true; + } + else + { + throw new IllegalStateException("Cannot delete authentication provider in state: " + state); + } + } + else if(desiredState == State.ACTIVE) + { + if ((state == State.INITIALISING || state == State.QUIESCED || state == State.STOPPED) && _state.compareAndSet(state, State.ACTIVE)) + { + try + { + _authManager.initialise(); + if (_preferencesProvider != null) + { + _preferencesProvider.setDesiredState(_preferencesProvider.getActualState(), State.ACTIVE); + } + return true; + } + catch(RuntimeException e) + { + _state.compareAndSet(State.ACTIVE, State.ERRORED); + if (_broker.isManagementMode()) + { + LOGGER.warn("Failed to activate authentication provider: " + getName(), e); + } + else + { + throw e; + } + } + } + else + { + throw new IllegalStateException("Cannot activate authentication provider in state: " + state); + } + } + else if (desiredState == State.QUIESCED) + { + if (state == State.INITIALISING && _state.compareAndSet(state, State.QUIESCED)) + { + return true; + } + } + else if(desiredState == State.STOPPED) + { + if (_state.compareAndSet(state, State.STOPPED)) + { + _authManager.close(); + if (_preferencesProvider != null) + { + _preferencesProvider.setDesiredState(_preferencesProvider.getActualState(), State.STOPPED); + } + return true; + } + else + { + throw new IllegalStateException("Cannot stop authentication provider in state: " + state); + } + } + + return false; + } + + @Override + public SubjectCreator getSubjectCreator() + { + return new SubjectCreator(_authManager, _broker.getGroupProviders()); + } + + @Override + protected void changeAttributes(Map attributes) + { + Map effectiveAttributes = super.generateEffectiveAttributes(attributes); + AuthenticationManager manager = validateAttributes(effectiveAttributes); + manager.initialise(); + super.changeAttributes(attributes); + _authManager = (T)manager; + + // if provider was previously in ERRORED state then set its state to ACTIVE + _state.compareAndSet(State.ERRORED, State.ACTIVE); + } + + private Map getAuthenticationManagerFactories() + { + QpidServiceLoader loader = new QpidServiceLoader(); + Iterable factories = loader.atLeastOneInstanceOf(AuthenticationManagerFactory.class); + Map factoryMap = new HashMap(); + for (AuthenticationManagerFactory factory : factories) + { + factoryMap.put(factory.getType(), factory); + } + return factoryMap; + } + + protected Collection createSupportedAttributes(Collection factoryAttributes) + { + List attributesNames = new ArrayList(AVAILABLE_ATTRIBUTES); + if (factoryAttributes != null) + { + attributesNames.addAll(factoryAttributes); + } + return Collections.unmodifiableCollection(attributesNames); + } + + protected AuthenticationManager validateAttributes(Map attributes) + { + super.validateChangeAttributes(attributes); + + String newName = (String)attributes.get(NAME); + String currentName = getName(); + if (!currentName.equals(newName)) + { + throw new IllegalConfigurationException("Changing the name of authentication provider is not supported"); + } + String newType = (String)attributes.get(AuthenticationManagerFactory.ATTRIBUTE_TYPE); + String currentType = (String)getAttribute(AuthenticationManagerFactory.ATTRIBUTE_TYPE); + if (!currentType.equals(newType)) + { + throw new IllegalConfigurationException("Changing the type of authentication provider is not supported"); + } + AuthenticationManagerFactory managerFactory = _factories.get(newType); + if (managerFactory == null) + { + throw new IllegalConfigurationException("Cannot find authentication provider factory for type " + newType); + } + AuthenticationManager manager = managerFactory.createInstance(attributes); + if (manager == null) + { + throw new IllegalConfigurationException("Cannot change authentication provider " + newName + " of type " + newType + " with the given attributes"); + } + return manager; + } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), AuthenticationProvider.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of authentication provider is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), AuthenticationProvider.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of authentication provider attributes is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), AuthenticationProvider.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of authentication provider attributes is denied"); + } + } + + public PreferencesProvider getPreferencesProvider() + { + return _preferencesProvider; + } + + public void setPreferencesProvider(PreferencesProvider provider) + { + if (AnonymousAuthenticationManagerFactory.PROVIDER_TYPE.equals(getAttribute(TYPE))) + { + throw new IllegalConfigurationException("Cannot set preferences provider for anonymous authentication provider"); + } + _preferencesProvider = provider; + } + + @SuppressWarnings("unchecked") + @Override + public C addChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + if(childClass == PreferencesProvider.class) + { + String name = MapValueConverter.getStringAttribute(PreferencesProvider.NAME, attributes); + PreferencesProvider pp = _preferencesProviderCreator.create(UUIDGenerator.generatePreferencesProviderUUID(name, getName()), attributes, this); + pp.setDesiredState(State.INITIALISING, State.ACTIVE); + _preferencesProvider = pp; + return (C)pp; + } + throw new IllegalArgumentException("Cannot create child of class " + childClass.getSimpleName()); + } + + public static class SimpleAuthenticationProviderAdapter extends AuthenticationProviderAdapter + { + + public SimpleAuthenticationProviderAdapter( + UUID id, Broker broker, AuthenticationManager authManager, Map attributes, Collection attributeNames, PreferencesProviderCreator preferencesProviderCreator) + { + super(id, broker,authManager, attributes, attributeNames, preferencesProviderCreator); + } + } + + public static class PrincipalDatabaseAuthenticationManagerAdapter + extends AuthenticationProviderAdapter + implements PasswordCredentialManagingAuthenticationProvider + { + public PrincipalDatabaseAuthenticationManagerAdapter( + UUID id, Broker broker, PrincipalDatabaseAuthenticationManager authManager, Map attributes, Collection attributeNames, PreferencesProviderCreator preferencesProviderCreator) + { + super(id, broker, authManager, attributes, attributeNames, preferencesProviderCreator); + } + + @Override + public boolean createUser(String username, String password, Map attributes) + { + if(getSecurityManager().authoriseUserOperation(Operation.CREATE, username)) + { + return getPrincipalDatabase().createPrincipal(new UsernamePrincipal(username), password.toCharArray()); + } + else + { + throw new AccessControlException("Do not have permission to create new user"); + } + } + + @Override + public void deleteUser(String username) throws AccountNotFoundException + { + if(getSecurityManager().authoriseUserOperation(Operation.DELETE, username)) + { + getPrincipalDatabase().deletePrincipal(new UsernamePrincipal(username)); + } + else + { + throw new AccessControlException("Cannot delete user " + username); + } + } + + private SecurityManager getSecurityManager() + { + return _broker.getSecurityManager(); + } + + private PrincipalDatabase getPrincipalDatabase() + { + return getAuthManager().getPrincipalDatabase(); + } + + @Override + public void setPassword(String username, String password) throws AccountNotFoundException + { + if(getSecurityManager().authoriseUserOperation(Operation.UPDATE, username)) + { + getPrincipalDatabase().updatePassword(new UsernamePrincipal(username), password.toCharArray()); + } + else + { + throw new AccessControlException("Do not have permission to set password"); + } + } + + @Override + public Map> getUsers() + { + + Map> users = new HashMap>(); + for(Principal principal : getPrincipalDatabase().getUsers()) + { + users.put(principal.getName(), Collections.emptyMap()); + } + return users; + } + + public void reload() throws IOException + { + getPrincipalDatabase().reload(); + } + + @Override + public C addChild(Class childClass, + Map attributes, + ConfiguredObject... otherParents) + { + if(childClass == User.class) + { + String username = (String) attributes.get("name"); + String password = (String) attributes.get("password"); + Principal p = new UsernamePrincipal(username); + + if(createUser(username, password,null)) + { + @SuppressWarnings("unchecked") + C pricipalAdapter = (C) new PrincipalAdapter(p); + return pricipalAdapter; + } + else + { + //TODO? Silly interface on the PrincipalDatabase at fault + throw new RuntimeException("Failed to create user"); + } + } + + return super.addChild(childClass, attributes, otherParents); + } + + @Override + public Collection getChildren(Class clazz) + { + if(clazz == User.class) + { + List users = getPrincipalDatabase().getUsers(); + Collection principals = new ArrayList(users.size()); + for(Principal user : users) + { + principals.add(new PrincipalAdapter(user)); + } + @SuppressWarnings("unchecked") + Collection unmodifiablePrincipals = (Collection) Collections.unmodifiableCollection(principals); + return unmodifiablePrincipals; + } + else + { + return super.getChildren(clazz); + } + } + + @Override + protected void childAdded(ConfiguredObject child) + { + if (child instanceof User) + { + // no-op, prevent storing users in the broker store + return; + } + super.childAdded(child); + } + + @Override + protected void childRemoved(ConfiguredObject child) + { + if (child instanceof User) + { + // no-op, as per above, users are not in the store + return; + } + super.childRemoved(child); + } + + private class PrincipalAdapter extends AbstractAdapter implements User + { + private final Principal _user; + + public PrincipalAdapter(Principal user) + { + super(UUIDGenerator.generateUserUUID(PrincipalDatabaseAuthenticationManagerAdapter.this.getName(), user.getName()), + PrincipalDatabaseAuthenticationManagerAdapter.this.getTaskExecutor()); + _user = user; + + } + + @Override + public void setPassword(String password) + { + try + { + PrincipalDatabaseAuthenticationManagerAdapter.this.setPassword(_user.getName(), password); + } + catch (AccountNotFoundException e) + { + throw new IllegalStateException(e); + } + } + + @Override + public String getName() + { + return _user.getName(); + } + + @Override + public String setName(String currentName, String desiredName) + throws IllegalStateException, AccessControlException + { + throw new IllegalStateException("Names cannot be updated"); + } + + @Override + public State getActualState() + { + return State.ACTIVE; + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException("Durability cannot be updated"); + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException("LifetimePolicy cannot be updated"); + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException("ttl cannot be updated"); + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public Collection getChildren(Class clazz) + { + return null; + } + + @Override + public C createChild(Class childClass, + Map attributes, + ConfiguredObject... otherParents) + { + return null; + } + + @Override + public Collection getAttributeNames() + { + return User.AVAILABLE_ATTRIBUTES; + } + + @Override + public Object getAttribute(String name) + { + if(ID.equals(name)) + { + return getId(); + } + else if(PASSWORD.equals(name)) + { + return null; // for security reasons we don't expose the password + } + else if(NAME.equals(name)) + { + return getName(); + } + return super.getAttribute(name); + } + + @Override + public boolean changeAttribute(String name, Object expected, Object desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + if(name.equals(PASSWORD)) + { + setPassword((String)desired); + return true; + } + return super.changeAttribute(name, expected, desired); + } + + @Override + protected boolean setState(State currentState, State desiredState) + throws IllegalStateTransitionException, AccessControlException + { + if(desiredState == State.DELETED) + { + try + { + String userName = _user.getName(); + deleteUser(userName); + PreferencesProvider preferencesProvider = getPreferencesProvider(); + if (preferencesProvider != null) + { + preferencesProvider.deletePreferences(userName); + } + } + catch (AccountNotFoundException e) + { + LOGGER.warn("Failed to delete user " + _user, e); + } + return true; + } + return false; + } + + @Override + public Map getPreferences() + { + PreferencesProvider preferencesProvider = getPreferencesProvider(); + if (preferencesProvider == null) + { + return null; + } + return preferencesProvider.getPreferences(this.getName()); + } + + @Override + public Object getPreference(String name) + { + Map preferences = getPreferences(); + if (preferences == null) + { + return null; + } + return preferences.get(name); + } + + @Override + public Map setPreferences(Map preferences) + { + PreferencesProvider preferencesProvider = getPreferencesProvider(); + if (preferencesProvider == null) + { + return null; + } + return preferencesProvider.setPreferences(this.getName(), preferences); + } + + @Override + public Map replacePreferences(Map newPreferences) + { + PreferencesProvider preferencesProvider = getPreferencesProvider(); + if (preferencesProvider == null) + { + return null; + } + Map preferences = preferencesProvider.deletePreferences(this.getName()); + preferencesProvider.setPreferences(this.getName(), newPreferences); + return preferences; + } + + private PreferencesProvider getPreferencesProvider() + { + return PrincipalDatabaseAuthenticationManagerAdapter.this.getPreferencesProvider(); + } + + } + + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactory.java new file mode 100644 index 0000000000..71bc3c631d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactory.java @@ -0,0 +1,111 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.PasswordCredentialManagingAuthenticationProvider; +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; +import org.apache.qpid.server.model.adapter.AuthenticationProviderAdapter.PrincipalDatabaseAuthenticationManagerAdapter; +import org.apache.qpid.server.model.adapter.AuthenticationProviderAdapter.SimpleAuthenticationProviderAdapter; + +public class AuthenticationProviderFactory +{ + private final Iterable _factories; + private Collection _supportedAuthenticationProviders; + private final PreferencesProviderCreator _preferencesProviderCreator; + + public AuthenticationProviderFactory(QpidServiceLoader authManagerFactoryServiceLoader, PreferencesProviderCreator preferencesProviderCreator) + { + _preferencesProviderCreator = preferencesProviderCreator; + _factories = authManagerFactoryServiceLoader.atLeastOneInstanceOf(AuthenticationManagerFactory.class); + List supportedAuthenticationProviders = new ArrayList(); + for (AuthenticationManagerFactory factory : _factories) + { + supportedAuthenticationProviders.add(factory.getType()); + } + _supportedAuthenticationProviders = Collections.unmodifiableCollection(supportedAuthenticationProviders); + } + + /** + * Creates {@link AuthenticationProvider} for given ID, {@link Broker} and attributes. + *

+ * The configured {@link AuthenticationManagerFactory}'s are used to try to create the {@link AuthenticationProvider}. + * The first non-null instance is returned. The factories are used in non-deterministic order. + */ + public AuthenticationProvider create(UUID id, Broker broker, Map attributes) + { + AuthenticationProviderAdapter provider = createAuthenticationProvider(id, broker, attributes); + provider.getAuthManager().onCreate(); + return provider; + } + + /** + * Recovers {@link AuthenticationProvider} for given ID, attributes and {@link Broker}. + *

+ * The configured {@link AuthenticationManagerFactory}'s are used to try to create the {@link AuthenticationProvider}. + * The first non-null instance is returned. The factories are used in non-deterministic order. + */ + public AuthenticationProvider recover(UUID id, Map attributes, Broker broker) + { + return createAuthenticationProvider(id, broker, attributes); + } + + private AuthenticationProviderAdapter createAuthenticationProvider(UUID id, Broker broker, Map attributes) + { + for (AuthenticationManagerFactory factory : _factories) + { + AuthenticationManager manager = factory.createInstance(attributes); + if (manager != null) + { + AuthenticationProviderAdapter authenticationProvider; + if (manager instanceof PrincipalDatabaseAuthenticationManager) + { + authenticationProvider = new PrincipalDatabaseAuthenticationManagerAdapter(id, broker, + (PrincipalDatabaseAuthenticationManager) manager, attributes, factory.getAttributeNames(), _preferencesProviderCreator); + } + else + { + authenticationProvider = new SimpleAuthenticationProviderAdapter(id, broker, manager, attributes, factory.getAttributeNames(), _preferencesProviderCreator); + } + return authenticationProvider; + } + } + + throw new IllegalArgumentException("No authentication provider factory found for configuration attributes " + attributes); + } + + public Collection getSupportedAuthenticationProviders() + { + return _supportedAuthenticationProviders; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BindingAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BindingAdapter.java new file mode 100644 index 0000000000..92b8f55f23 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BindingAdapter.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.model.adapter; + +import java.security.AccessControlException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; + +final class BindingAdapter extends AbstractAdapter implements Binding +{ + private final org.apache.qpid.server.binding.Binding _binding; + private Statistics _statistics = NoStatistics.getInstance(); + private final ExchangeAdapter _exchange; + private QueueAdapter _queue; + + public BindingAdapter(final org.apache.qpid.server.binding.Binding binding, + ExchangeAdapter exchangeAdapter, + QueueAdapter queueAdapter) + { + super(binding.getId(), queueAdapter.getTaskExecutor()); + _binding = binding; + _exchange = exchangeAdapter; + _queue = queueAdapter; + addParent(Queue.class, queueAdapter); + addParent(Exchange.class, exchangeAdapter); + } + + + public ExchangeAdapter getExchange() + { + return _exchange; + } + + public QueueAdapter getQueue() + { + return _queue; + } + + public String getName() + { + return _binding.getBindingKey(); + } + + public String setName(final String currentName, final String desiredName) + throws IllegalStateException, AccessControlException + { + return null; //TODO + } + + public State getActualState() + { + return null; //TODO + } + + public boolean isDurable() + { + return _binding.getQueue().isDurable() && _binding.getExchange().isDurable(); + } + + public void setDurable(final boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + //TODO + } + + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + public LifetimePolicy setLifetimePolicy(final LifetimePolicy expected, final LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return null; //TODO + } + + public long getTimeToLive() + { + return 0; //TODO + } + + public long setTimeToLive(final long expected, final long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return 0; //TODO + } + + public Statistics getStatistics() + { + return _statistics; + } + + @Override + public Collection getChildren(Class clazz) + { + return Collections.emptySet(); + } + + @Override + public C createChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + throw new IllegalArgumentException("Cannot add children to a binding"); + } + + public Map getArguments() + { + return new HashMap (_binding.getArguments()); + } + + public void delete() + { + try + { + _exchange.getExchange().removeBinding(_binding); + } + catch(AMQSecurityException e) + { + throw new AccessControlException(e.getMessage()); + } + catch(AMQInternalException e) + { + throw new IllegalStateException(e); + } + } + + @Override + public Object getAttribute(final String name) + { + if(ID.equals(name)) + { + return getId(); + } + else if(NAME.equals(name)) + { + return getName(); + } + else if(STATE.equals(name)) + { + + } + else if(DURABLE.equals(name)) + { + return _queue.isDurable() && _exchange.isDurable(); + } + else if(LIFETIME_POLICY.equals(name)) + { + return _queue.getLifetimePolicy() == LifetimePolicy.AUTO_DELETE || _exchange.getLifetimePolicy() == LifetimePolicy.AUTO_DELETE ? LifetimePolicy.AUTO_DELETE : LifetimePolicy.PERMANENT; + } + else if(TIME_TO_LIVE.equals(name)) + { + + } + else if(CREATED.equals(name)) + { + + } + else if(UPDATED.equals(name)) + { + + } + else if(EXCHANGE.equals(name)) + { + return _exchange.getName(); + } + else if(QUEUE.equals(name)) + { + return _queue.getName(); + } + else if(ARGUMENTS.equals(name)) + { + return getArguments(); + } + + return super.getAttribute(name); //TODO + } + + @Override + public Collection getAttributeNames() + { + return Binding.AVAILABLE_ATTRIBUTES; + } + + @Override + protected boolean setState(State currentState, State desiredState) throws IllegalStateTransitionException, + AccessControlException + { + if (desiredState == State.DELETED) + { + delete(); + return true; + } + return false; + } + + @Override + public Object setAttribute(final String name, final Object expected, final Object desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on binding is not supported."); + } + + @Override + public void setAttributes(final Map attributes) throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on binding is not supported."); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java new file mode 100644 index 0000000000..aec78ba414 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/BrokerAdapter.java @@ -0,0 +1,1267 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.lang.reflect.Type; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.AccessControlException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.configuration.BrokerConfigurationStoreCreator; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.logging.LogRecorder; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.actors.BrokerActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.BrokerMessages; +import org.apache.qpid.server.model.AccessControlProvider; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfigurationChangeListener; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Model; +import org.apache.qpid.server.model.Plugin; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.adapter.AuthenticationProviderAdapter.SimpleAuthenticationProviderAdapter; +import org.apache.qpid.server.plugin.VirtualHostFactory; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.manager.SimpleAuthenticationManager; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.MessageStoreCreator; +import org.apache.qpid.server.util.MapValueConverter; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public class BrokerAdapter extends AbstractAdapter implements Broker, ConfigurationChangeListener +{ + private static final Logger LOGGER = Logger.getLogger(BrokerAdapter.class); + + @SuppressWarnings("serial") + public static final Map ATTRIBUTE_TYPES = Collections.unmodifiableMap(new HashMap(){{ + put(QUEUE_ALERT_THRESHOLD_MESSAGE_AGE, Long.class); + put(QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, Long.class); + put(QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, Long.class); + put(QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE, Long.class); + put(QUEUE_ALERT_REPEAT_GAP, Long.class); + put(QUEUE_FLOW_CONTROL_SIZE_BYTES, Long.class); + put(QUEUE_FLOW_CONTROL_RESUME_SIZE_BYTES, Long.class); + put(VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD, Long.class); + + put(QUEUE_DEAD_LETTER_QUEUE_ENABLED, Boolean.class); + put(STATISTICS_REPORTING_RESET_ENABLED, Boolean.class); + + put(QUEUE_MAXIMUM_DELIVERY_ATTEMPTS, Integer.class); + put(CONNECTION_SESSION_COUNT_LIMIT, Integer.class); + put(CONNECTION_HEART_BEAT_DELAY, Integer.class); + put(CONNECTION_CLOSE_WHEN_NO_ROUTE, Boolean.class); + put(STATISTICS_REPORTING_PERIOD, Integer.class); + + put(NAME, String.class); + put(DEFAULT_VIRTUAL_HOST, String.class); + + put(VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE, Long.class); + put(VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_WARN, Long.class); + put(VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE, Long.class); + put(VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_WARN, Long.class); + put(MODEL_VERSION, String.class); + put(STORE_VERSION, String.class); + }}); + + public static final int DEFAULT_STATISTICS_REPORTING_PERIOD = 0; + public static final boolean DEFAULT_STATISTICS_REPORTING_RESET_ENABLED = false; + public static final long DEFAULT_ALERT_REPEAT_GAP = 30000l; + public static final long DEFAULT_ALERT_THRESHOLD_MESSAGE_AGE = 0l; + public static final long DEFAULT_ALERT_THRESHOLD_MESSAGE_COUNT = 0l; + public static final long DEFAULT_ALERT_THRESHOLD_MESSAGE_SIZE = 0l; + public static final long DEFAULT_ALERT_THRESHOLD_QUEUE_DEPTH = 0l; + public static final boolean DEFAULT_DEAD_LETTER_QUEUE_ENABLED = false; + public static final int DEFAULT_MAXIMUM_DELIVERY_ATTEMPTS = 0; + public static final long DEFAULT_FLOW_CONTROL_RESUME_SIZE_BYTES = 0l; + public static final long DEFAULT_FLOW_CONTROL_SIZE_BYTES = 0l; + public static final long DEFAULT_HOUSEKEEPING_CHECK_PERIOD = 30000l; + public static final int DEFAULT_HEART_BEAT_DELAY = 0; + public static final int DEFAULT_SESSION_COUNT_LIMIT = 256; + public static final String DEFAULT_NAME = "QpidBroker"; + public static final long DEFAULT_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE = 0l; + public static final long DEFAULT_STORE_TRANSACTION_IDLE_TIMEOUT_WARN = 0l; + public static final long DEFAULT_STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE = 0l; + public static final long DEFAULT_STORE_TRANSACTION_OPEN_TIMEOUT_WARN = 0l; + public static final boolean DEFAULT_CONNECTION_CLOSE_WHEN_NO_ROUTE = true; + + @SuppressWarnings("serial") + private static final Map DEFAULTS = Collections.unmodifiableMap(new HashMap(){{ + put(Broker.STATISTICS_REPORTING_PERIOD, DEFAULT_STATISTICS_REPORTING_PERIOD); + put(Broker.STATISTICS_REPORTING_RESET_ENABLED, DEFAULT_STATISTICS_REPORTING_RESET_ENABLED); + put(Broker.QUEUE_ALERT_REPEAT_GAP, DEFAULT_ALERT_REPEAT_GAP); + put(Broker.QUEUE_ALERT_THRESHOLD_MESSAGE_AGE, DEFAULT_ALERT_THRESHOLD_MESSAGE_AGE); + put(Broker.QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, DEFAULT_ALERT_THRESHOLD_MESSAGE_COUNT); + put(Broker.QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE, DEFAULT_ALERT_THRESHOLD_MESSAGE_SIZE); + put(Broker.QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, DEFAULT_ALERT_THRESHOLD_QUEUE_DEPTH); + put(Broker.QUEUE_DEAD_LETTER_QUEUE_ENABLED, DEFAULT_DEAD_LETTER_QUEUE_ENABLED); + put(Broker.QUEUE_MAXIMUM_DELIVERY_ATTEMPTS, DEFAULT_MAXIMUM_DELIVERY_ATTEMPTS); + put(Broker.QUEUE_FLOW_CONTROL_RESUME_SIZE_BYTES, DEFAULT_FLOW_CONTROL_RESUME_SIZE_BYTES); + put(Broker.QUEUE_FLOW_CONTROL_SIZE_BYTES, DEFAULT_FLOW_CONTROL_SIZE_BYTES); + put(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD, DEFAULT_HOUSEKEEPING_CHECK_PERIOD); + put(Broker.CONNECTION_HEART_BEAT_DELAY, DEFAULT_HEART_BEAT_DELAY); + put(Broker.CONNECTION_SESSION_COUNT_LIMIT, DEFAULT_SESSION_COUNT_LIMIT); + put(Broker.CONNECTION_CLOSE_WHEN_NO_ROUTE, DEFAULT_CONNECTION_CLOSE_WHEN_NO_ROUTE); + put(Broker.NAME, DEFAULT_NAME); + put(Broker.VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE, DEFAULT_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE); + put(Broker.VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_WARN, DEFAULT_STORE_TRANSACTION_IDLE_TIMEOUT_WARN); + put(Broker.VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE, DEFAULT_STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE); + put(Broker.VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_WARN, DEFAULT_STORE_TRANSACTION_OPEN_TIMEOUT_WARN); + }}); + + private String[] POSITIVE_NUMERIC_ATTRIBUTES = { QUEUE_ALERT_THRESHOLD_MESSAGE_AGE, QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, + QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE, QUEUE_ALERT_REPEAT_GAP, QUEUE_FLOW_CONTROL_SIZE_BYTES, + QUEUE_FLOW_CONTROL_RESUME_SIZE_BYTES, QUEUE_MAXIMUM_DELIVERY_ATTEMPTS, VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD, CONNECTION_SESSION_COUNT_LIMIT, + CONNECTION_HEART_BEAT_DELAY, STATISTICS_REPORTING_PERIOD, VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE, + VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_WARN, VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE, + VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_WARN}; + + + private final StatisticsGatherer _statisticsGatherer; + private final VirtualHostRegistry _virtualHostRegistry; + private final LogRecorder _logRecorder; + private final RootMessageLogger _rootMessageLogger; + private StatisticsAdapter _statistics; + + private final Map _vhostAdapters = new HashMap(); + private final Map _portAdapters = new HashMap(); + private final Map _stillInUsePortNumbers = new HashMap(); + private final Map _authenticationProviders = new HashMap(); + private final Map _groupProviders = new HashMap(); + private final Map _plugins = new HashMap(); + private final Map _keyStores = new HashMap(); + private final Map _trustStores = new HashMap(); + private final Map _accessControlProviders = new HashMap(); + + private final GroupProviderFactory _groupProviderFactory; + private final AuthenticationProviderFactory _authenticationProviderFactory; + private final AccessControlProviderFactory _accessControlProviderFactory; + private final PreferencesProviderCreator _preferencesProviderCreator; + private final PortFactory _portFactory; + private final SecurityManager _securityManager; + + private final Collection _supportedVirtualHostStoreTypes; + private Collection _supportedBrokerStoreTypes; + private final ConfigurationEntryStore _brokerStore; + + private AuthenticationProvider _managementAuthenticationProvider; + private BrokerOptions _brokerOptions; + + public BrokerAdapter(UUID id, Map attributes, StatisticsGatherer statisticsGatherer, VirtualHostRegistry virtualHostRegistry, + LogRecorder logRecorder, RootMessageLogger rootMessageLogger, AuthenticationProviderFactory authenticationProviderFactory, + GroupProviderFactory groupProviderFactory, AccessControlProviderFactory accessControlProviderFactory, PortFactory portFactory, + PreferencesProviderCreator preferencesProviderCreatory, TaskExecutor taskExecutor, ConfigurationEntryStore brokerStore, BrokerOptions brokerOptions) + { + super(id, DEFAULTS, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES), taskExecutor); + _statisticsGatherer = statisticsGatherer; + _virtualHostRegistry = virtualHostRegistry; + _logRecorder = logRecorder; + _rootMessageLogger = rootMessageLogger; + _statistics = new StatisticsAdapter(statisticsGatherer); + _authenticationProviderFactory = authenticationProviderFactory; + _preferencesProviderCreator = preferencesProviderCreatory; + _groupProviderFactory = groupProviderFactory; + _accessControlProviderFactory = accessControlProviderFactory; + _portFactory = portFactory; + _brokerOptions = brokerOptions; + _securityManager = new SecurityManager(this, _brokerOptions.isManagementMode()); + _supportedVirtualHostStoreTypes = new MessageStoreCreator().getStoreTypes(); + _supportedBrokerStoreTypes = new BrokerConfigurationStoreCreator().getStoreTypes(); + _brokerStore = brokerStore; + if (_brokerOptions.isManagementMode()) + { + AuthenticationManager authManager = new SimpleAuthenticationManager(BrokerOptions.MANAGEMENT_MODE_USER_NAME, _brokerOptions.getManagementModePassword()); + AuthenticationProvider authenticationProvider = new SimpleAuthenticationProviderAdapter(UUID.randomUUID(), this, + authManager, Collections. emptyMap(), Collections. emptySet(), _preferencesProviderCreator); + _managementAuthenticationProvider = authenticationProvider; + } + } + + public Collection getVirtualHosts() + { + synchronized(_vhostAdapters) + { + return new ArrayList(_vhostAdapters.values()); + } + } + + public Collection getPorts() + { + synchronized (_portAdapters) + { + final ArrayList ports = new ArrayList(_portAdapters.values()); + return ports; + } + } + + public Collection getAuthenticationProviders() + { + synchronized (_authenticationProviders) + { + return new ArrayList(_authenticationProviders.values()); + } + } + + public AuthenticationProvider findAuthenticationProviderByName(String authenticationProviderName) + { + if (isManagementMode()) + { + return _managementAuthenticationProvider; + } + Collection providers = getAuthenticationProviders(); + for (AuthenticationProvider authenticationProvider : providers) + { + if (authenticationProvider.getName().equals(authenticationProviderName)) + { + return authenticationProvider; + } + } + return null; + } + + public KeyStore findKeyStoreByName(String keyStoreName) + { + synchronized(_keyStores) + { + return _keyStores.get(keyStoreName); + } + } + + public TrustStore findTrustStoreByName(String trustStoreName) + { + synchronized(_trustStores) + { + return _trustStores.get(trustStoreName); + } + } + + @Override + public Collection getGroupProviders() + { + synchronized (_groupProviders) + { + final ArrayList groupManagers = + new ArrayList(_groupProviders.values()); + return groupManagers; + } + } + + private VirtualHost createVirtualHost(final Map attributes) + throws AccessControlException, IllegalArgumentException + { + final VirtualHostAdapter virtualHostAdapter = new VirtualHostAdapter(UUID.randomUUID(), attributes, this, + _statisticsGatherer, getTaskExecutor()); + addVirtualHost(virtualHostAdapter); + + // permission has already been granted to create the virtual host + // disable further access check on other operations, e.g. create exchange + SecurityManager.setAccessChecksDisabled(true); + try + { + virtualHostAdapter.setDesiredState(State.INITIALISING, State.ACTIVE); + } + finally + { + SecurityManager.setAccessChecksDisabled(false); + } + return virtualHostAdapter; + } + + private boolean deleteVirtualHost(final VirtualHost vhost) throws AccessControlException, IllegalStateException + { + synchronized (_vhostAdapters) + { + _vhostAdapters.remove(vhost.getName()); + } + vhost.removeChangeListener(this); + return true; + } + + public String getName() + { + return (String)getAttribute(NAME); + } + + public String setName(final String currentName, final String desiredName) + throws IllegalStateException, AccessControlException + { + return null; //TODO + } + + + public State getActualState() + { + return null; //TODO + } + + + public boolean isDurable() + { + return true; + } + + public void setDurable(final boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + public LifetimePolicy setLifetimePolicy(final LifetimePolicy expected, final LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + public long getTimeToLive() + { + return 0; + } + + public long setTimeToLive(final long expected, final long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + public Statistics getStatistics() + { + return _statistics; + } + + @SuppressWarnings("unchecked") + @Override + public Collection getChildren(Class clazz) + { + if(clazz == VirtualHost.class) + { + return (Collection) getVirtualHosts(); + } + else if(clazz == Port.class) + { + return (Collection) getPorts(); + } + else if(clazz == AccessControlProvider.class) + { + return (Collection) getAccessControlProviders(); + } + else if(clazz == AuthenticationProvider.class) + { + return (Collection) getAuthenticationProviders(); + } + else if(clazz == GroupProvider.class) + { + return (Collection) getGroupProviders(); + } + else if(clazz == KeyStore.class) + { + return (Collection) getKeyStores(); + } + else if(clazz == TrustStore.class) + { + return (Collection) getTrustStores(); + } + else if(clazz == Plugin.class) + { + return (Collection) getPlugins(); + } + + return Collections.emptySet(); + } + + @SuppressWarnings("unchecked") + @Override + public C addChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + if(childClass == VirtualHost.class) + { + return (C) createVirtualHost(attributes); + } + else if(childClass == Port.class) + { + return (C) createPort(attributes); + } + else if(childClass == AccessControlProvider.class) + { + return (C) createAccessControlProvider(attributes); + } + else if(childClass == AuthenticationProvider.class) + { + return (C) createAuthenticationProvider(attributes); + } + else if(childClass == KeyStore.class) + { + return (C) createKeyStore(attributes); + } + else if(childClass == TrustStore.class) + { + return (C) createTrustStore(attributes); + } + else if(childClass == GroupProvider.class) + { + return (C) createGroupProvider(attributes); + } + else + { + throw new IllegalArgumentException("Cannot create child of class " + childClass.getSimpleName()); + } + } + + /** + * Called when adding a new port via the management interface + */ + private Port createPort(Map attributes) + { + Port port = _portFactory.createPort(UUID.randomUUID(), this, attributes); + addPort(port); + + //1. AMQP ports are disabled during ManagementMode. + //2. The management plugins can currently only start ports at broker startup and + // not when they are newly created via the management interfaces. + //3. When active ports are deleted, or their port numbers updated, the broker must be + // restarted for it to take effect so we can't reuse port numbers until it is. + boolean quiesce = isManagementMode() || !(port instanceof AmqpPortAdapter) || isPreviouslyUsedPortNumber(port); + + port.setDesiredState(State.INITIALISING, quiesce ? State.QUIESCED : State.ACTIVE); + + return port; + } + + private void addPort(Port port) + { + synchronized (_portAdapters) + { + int portNumber = port.getPort(); + String portName = port.getName(); + UUID portId = port.getId(); + + for(Port p : _portAdapters.values()) + { + if(portNumber == p.getPort()) + { + throw new IllegalConfigurationException("Can't add port " + portName + " because port number " + portNumber + " is already configured for port " + p.getName()); + } + + if(portName == p.getName()) + { + throw new IllegalConfigurationException("Can't add Port because one with name " + portName + " already exists"); + } + + if(portId == p.getId()) + { + throw new IllegalConfigurationException("Can't add Port because one with id " + portId + " already exists"); + } + } + + _portAdapters.put(port.getId(), port); + } + port.addChangeListener(this); + } + + private AccessControlProvider createAccessControlProvider(Map attributes) + { + AccessControlProvider accessControlProvider = null; + synchronized (_accessControlProviders) + { + accessControlProvider = _accessControlProviderFactory.create(UUID.randomUUID(), this, attributes); + addAccessControlProvider(accessControlProvider); + } + + boolean quiesce = isManagementMode() ; + accessControlProvider.setDesiredState(State.INITIALISING, quiesce ? State.QUIESCED : State.ACTIVE); + + return accessControlProvider; + } + + /** + * @throws IllegalConfigurationException if an AuthenticationProvider with the same name already exists + */ + private void addAccessControlProvider(AccessControlProvider accessControlProvider) + { + String name = accessControlProvider.getName(); + synchronized (_authenticationProviders) + { + if (_accessControlProviders.containsKey(accessControlProvider.getId())) + { + throw new IllegalConfigurationException("Can't add AccessControlProvider because one with id " + accessControlProvider.getId() + " already exists"); + } + for (AccessControlProvider provider : _accessControlProviders.values()) + { + if (provider.getName().equals(name)) + { + throw new IllegalConfigurationException("Can't add AccessControlProvider because one with name " + name + " already exists"); + } + } + _accessControlProviders.put(accessControlProvider.getId(), accessControlProvider); + } + + accessControlProvider.addChangeListener(this); + accessControlProvider.addChangeListener(_securityManager); + } + + private boolean deleteAccessControlProvider(AccessControlProvider accessControlProvider) + { + AccessControlProvider removedAccessControlProvider = null; + synchronized (_accessControlProviders) + { + removedAccessControlProvider = _accessControlProviders.remove(accessControlProvider.getId()); + } + + if(removedAccessControlProvider != null) + { + removedAccessControlProvider.removeChangeListener(this); + removedAccessControlProvider.removeChangeListener(_securityManager); + } + + return removedAccessControlProvider != null; + } + + private AuthenticationProvider createAuthenticationProvider(Map attributes) + { + AuthenticationProvider authenticationProvider = _authenticationProviderFactory.create(UUID.randomUUID(), this, attributes); + authenticationProvider.setDesiredState(State.INITIALISING, State.ACTIVE); + addAuthenticationProvider(authenticationProvider); + return authenticationProvider; + } + + /** + * @throws IllegalConfigurationException if an AuthenticationProvider with the same name already exists + */ + private void addAuthenticationProvider(AuthenticationProvider authenticationProvider) + { + String name = authenticationProvider.getName(); + synchronized (_authenticationProviders) + { + if (_authenticationProviders.containsKey(authenticationProvider.getId())) + { + throw new IllegalConfigurationException("Cannot add AuthenticationProvider because one with id " + authenticationProvider.getId() + " already exists"); + } + for (AuthenticationProvider provider : _authenticationProviders.values()) + { + if (provider.getName().equals(name)) + { + throw new IllegalConfigurationException("Cannot add AuthenticationProvider because one with name " + name + " already exists"); + } + } + _authenticationProviders.put(authenticationProvider.getId(), authenticationProvider); + } + authenticationProvider.addChangeListener(this); + } + + private GroupProvider createGroupProvider(Map attributes) + { + GroupProvider groupProvider = _groupProviderFactory.create(UUID.randomUUID(), this, attributes); + groupProvider.setDesiredState(State.INITIALISING, State.ACTIVE); + addGroupProvider(groupProvider); + return groupProvider; + } + + private void addGroupProvider(GroupProvider groupProvider) + { + synchronized (_groupProviders) + { + String name = groupProvider.getName(); + if(_groupProviders.containsKey(name)) + { + throw new IllegalConfigurationException("Cannot add GroupProvider because one with name " + name + " already exists"); + } + _groupProviders.put(name, groupProvider); + } + groupProvider.addChangeListener(this); + } + + private boolean deleteGroupProvider(GroupProvider groupProvider) + { + GroupProvider removedGroupProvider = null; + synchronized (_groupProviders) + { + removedGroupProvider = _groupProviders.remove(groupProvider.getName()); + } + + if(removedGroupProvider != null) + { + removedGroupProvider.removeChangeListener(this); + } + + return removedGroupProvider != null; + } + + private KeyStore createKeyStore(Map attributes) + { + KeyStore keyStore = new KeyStoreAdapter(UUIDGenerator.generateRandomUUID(), this, attributes); + addKeyStore(keyStore); + + return keyStore; + } + + private TrustStore createTrustStore(Map attributes) + { + TrustStore trustStore = new TrustStoreAdapter(UUIDGenerator.generateRandomUUID(), this, attributes); + addTrustStore(trustStore); + + return trustStore; + } + + private void addKeyStore(KeyStore keyStore) + { + synchronized (_keyStores) + { + if(_keyStores.containsKey(keyStore.getName())) + { + throw new IllegalConfigurationException("Can't add KeyStore because one with name " + keyStore.getName() + " already exists"); + } + _keyStores.put(keyStore.getName(), keyStore); + } + keyStore.addChangeListener(this); + } + + private boolean deleteKeyStore(KeyStore object) + { + synchronized(_keyStores) + { + String name = object.getName(); + KeyStore removedKeyStore = _keyStores.remove(name); + if(removedKeyStore != null) + { + removedKeyStore.removeChangeListener(this); + } + + return removedKeyStore != null; + } + } + + private void addTrustStore(TrustStore trustStore) + { + synchronized (_trustStores) + { + if(_trustStores.containsKey(trustStore.getName())) + { + throw new IllegalConfigurationException("Can't add TrustStore because one with name " + trustStore.getName() + " already exists"); + } + _trustStores.put(trustStore.getName(), trustStore); + } + trustStore.addChangeListener(this); + } + + private boolean deleteTrustStore(TrustStore object) + { + synchronized(_trustStores) + { + String name = object.getName(); + TrustStore removedTrustStore = _trustStores.remove(name); + if(removedTrustStore != null) + { + removedTrustStore.removeChangeListener(this); + } + + return removedTrustStore != null; + } + } + + @Override + public Collection getAttributeNames() + { + return AVAILABLE_ATTRIBUTES; + } + + @Override + public Object getAttribute(String name) + { + if(ID.equals(name)) + { + return getId(); + } + else if(STATE.equals(name)) + { + return State.ACTIVE; + } + else if(DURABLE.equals(name)) + { + return isDurable(); + } + else if(LIFETIME_POLICY.equals(name)) + { + return LifetimePolicy.PERMANENT; + } + else if(TIME_TO_LIVE.equals(name)) + { + // TODO + } + else if(CREATED.equals(name)) + { + // TODO + } + else if(UPDATED.equals(name)) + { + // TODO + } + else if(BUILD_VERSION.equals(name)) + { + return QpidProperties.getBuildVersion(); + } + else if(BYTES_RETAINED.equals(name)) + { + // TODO + } + else if(OPERATING_SYSTEM.equals(name)) + { + return System.getProperty("os.name") + " " + + System.getProperty("os.version") + " " + + System.getProperty("os.arch"); + } + else if(PLATFORM.equals(name)) + { + return System.getProperty("java.vendor") + " " + + System.getProperty("java.runtime.version", System.getProperty("java.version")); + } + else if(PROCESS_PID.equals(name)) + { + // TODO + } + else if(PRODUCT_VERSION.equals(name)) + { + return QpidProperties.getReleaseVersion(); + } + else if(SUPPORTED_BROKER_STORE_TYPES.equals(name)) + { + return _supportedBrokerStoreTypes; + } + else if(SUPPORTED_VIRTUALHOST_STORE_TYPES.equals(name)) + { + return _supportedVirtualHostStoreTypes; + } + else if(SUPPORTED_VIRTUALHOST_TYPES.equals(name)) + { + return VirtualHostFactory.TYPES.get(); + } + else if(SUPPORTED_AUTHENTICATION_PROVIDERS.equals(name)) + { + return _authenticationProviderFactory.getSupportedAuthenticationProviders(); + } + else if (SUPPORTED_PREFERENCES_PROVIDERS_TYPES.equals(name)) + { + return _preferencesProviderCreator.getSupportedPreferencesProviders(); + } + else if (MODEL_VERSION.equals(name)) + { + return Model.MODEL_VERSION; + } + else if (STORE_VERSION.equals(name)) + { + return _brokerStore.getVersion(); + } + else if (STORE_TYPE.equals(name)) + { + return _brokerStore.getType(); + } + else if (STORE_PATH.equals(name)) + { + return _brokerStore.getStoreLocation(); + } + return super.getAttribute(name); + } + + private boolean deletePort(State oldState, Port portAdapter) + { + Port removedPort = null; + synchronized (_portAdapters) + { + removedPort = _portAdapters.remove(portAdapter.getId()); + } + + if (removedPort != null) + { + removedPort.removeChangeListener(this); + + if(oldState == State.ACTIVE) + { + //Record the originally used port numbers of previously-active ports being deleted, to ensure + //when creating new ports we don't try to re-bind a port number that we are currently still using + recordPreviouslyUsedPortNumberIfNecessary(removedPort, removedPort.getPort()); + } + } + + return removedPort != null; + } + + private boolean deleteAuthenticationProvider(AuthenticationProvider authenticationProvider) + { + AuthenticationProvider removedAuthenticationProvider = null; + synchronized (_authenticationProviders) + { + removedAuthenticationProvider = _authenticationProviders.remove(authenticationProvider.getId()); + } + + if(removedAuthenticationProvider != null) + { + removedAuthenticationProvider.removeChangeListener(this); + } + + return removedAuthenticationProvider != null; + } + + private void addVirtualHost(VirtualHost virtualHost) + { + synchronized (_vhostAdapters) + { + String name = virtualHost.getName(); + if (_vhostAdapters.containsKey(name)) + { + throw new IllegalConfigurationException("Virtual host with name " + name + " is already specified!"); + } + _vhostAdapters.put(name, virtualHost); + } + virtualHost.addChangeListener(this); + } + + @Override + public boolean setState(State currentState, State desiredState) + { + if (desiredState == State.ACTIVE) + { + changeState(_groupProviders, currentState, State.ACTIVE, false); + changeState(_authenticationProviders, currentState, State.ACTIVE, false); + changeState(_accessControlProviders, currentState, State.ACTIVE, false); + + CurrentActor.set(new BrokerActor(getRootMessageLogger())); + try + { + changeState(_vhostAdapters, currentState, State.ACTIVE, false); + } + finally + { + CurrentActor.remove(); + } + + changeState(_portAdapters, currentState,State.ACTIVE, false); + changeState(_plugins, currentState,State.ACTIVE, false); + + if (isManagementMode()) + { + CurrentActor.get().message(BrokerMessages.MANAGEMENT_MODE(BrokerOptions.MANAGEMENT_MODE_USER_NAME, _brokerOptions.getManagementModePassword())); + } + return true; + } + else if (desiredState == State.STOPPED) + { + changeState(_plugins, currentState,State.STOPPED, true); + changeState(_portAdapters, currentState, State.STOPPED, true); + changeState(_vhostAdapters,currentState, State.STOPPED, true); + changeState(_authenticationProviders, currentState, State.STOPPED, true); + changeState(_groupProviders, currentState, State.STOPPED, true); + return true; + } + return false; + } + + private void changeState(Map configuredObjectMap, State currentState, State desiredState, boolean swallowException) + { + synchronized(configuredObjectMap) + { + Collection adapters = configuredObjectMap.values(); + for (ConfiguredObject configuredObject : adapters) + { + if (State.ACTIVE.equals(desiredState) && State.QUIESCED.equals(configuredObject.getActualState())) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug(configuredObject + " cannot be activated as it is " +State.QUIESCED); + } + continue; + } + try + { + configuredObject.setDesiredState(currentState, desiredState); + } + catch(RuntimeException e) + { + if (swallowException) + { + LOGGER.error("Failed to stop " + configuredObject, e); + } + else + { + throw e; + } + } + } + } + } + + @Override + public void stateChanged(ConfiguredObject object, State oldState, State newState) + { + if(newState == State.DELETED) + { + boolean childDeleted = false; + if(object instanceof AuthenticationProvider) + { + childDeleted = deleteAuthenticationProvider((AuthenticationProvider)object); + } + else if(object instanceof AccessControlProvider) + { + childDeleted = deleteAccessControlProvider((AccessControlProvider)object); + } + else if(object instanceof Port) + { + childDeleted = deletePort(oldState, (Port)object); + } + else if(object instanceof VirtualHost) + { + childDeleted = deleteVirtualHost((VirtualHost)object); + } + else if(object instanceof GroupProvider) + { + childDeleted = deleteGroupProvider((GroupProvider)object); + } + else if(object instanceof KeyStore) + { + childDeleted = deleteKeyStore((KeyStore)object); + } + else if(object instanceof TrustStore) + { + childDeleted = deleteTrustStore((TrustStore)object); + } + + if(childDeleted) + { + childRemoved(object); + } + } + } + + @Override + public void childAdded(ConfiguredObject object, ConfiguredObject child) + { + // no-op + } + + @Override + public void childRemoved(ConfiguredObject object, ConfiguredObject child) + { + // no-op + } + + @Override + public void attributeSet(ConfiguredObject object, String attributeName, Object oldAttributeValue, Object newAttributeValue) + { + if(object instanceof Port) + { + //Record all the originally used port numbers of active ports, to ensure that when + //creating new ports we don't try to re-bind a port number that we are still using + if(attributeName == Port.PORT && object.getActualState() == State.ACTIVE) + { + recordPreviouslyUsedPortNumberIfNecessary((Port) object, (Integer)oldAttributeValue); + } + } + } + + private void addPlugin(ConfiguredObject plugin) + { + synchronized(_plugins) + { + if (_plugins.containsKey(plugin.getId())) + { + throw new IllegalConfigurationException("Plugin with id '" + plugin.getId() + "' is already registered!"); + } + _plugins.put(plugin.getId(), plugin); + } + plugin.addChangeListener(this); + } + + + private Collection getPlugins() + { + synchronized(_plugins) + { + return Collections.unmodifiableCollection(_plugins.values()); + } + } + + public void recoverChild(ConfiguredObject object) + { + if(object instanceof AuthenticationProvider) + { + addAuthenticationProvider((AuthenticationProvider)object); + } + else if(object instanceof AccessControlProvider) + { + addAccessControlProvider((AccessControlProvider)object); + } + else if(object instanceof Port) + { + addPort((Port)object); + } + else if(object instanceof VirtualHost) + { + addVirtualHost((VirtualHost)object); + } + else if(object instanceof GroupProvider) + { + addGroupProvider((GroupProvider)object); + } + else if(object instanceof KeyStore) + { + addKeyStore((KeyStore)object); + } + else if(object instanceof TrustStore) + { + addTrustStore((TrustStore)object); + } + else if(object instanceof Plugin) + { + addPlugin(object); + } + else + { + throw new IllegalArgumentException("Attempted to recover unexpected type of configured object: " + object.getClass().getName()); + } + } + + @Override + public RootMessageLogger getRootMessageLogger() + { + return _rootMessageLogger; + } + + @Override + public SecurityManager getSecurityManager() + { + return _securityManager; + } + + @Override + public LogRecorder getLogRecorder() + { + return _logRecorder; + } + + @Override + public VirtualHost findVirtualHostByName(String name) + { + return _vhostAdapters.get(name); + } + + @Override + public SubjectCreator getSubjectCreator(SocketAddress localAddress) + { + AuthenticationProvider provider = getAuthenticationProvider(localAddress); + + if(provider == null) + { + throw new IllegalConfigurationException("Unable to determine authentication provider for address: " + localAddress); + } + + return provider.getSubjectCreator(); + } + + @Override + public AuthenticationProvider getAuthenticationProvider(SocketAddress localAddress) + { + InetSocketAddress inetSocketAddress = (InetSocketAddress)localAddress; + AuthenticationProvider provider = null; + Collection ports = getPorts(); + for (Port p : ports) + { + if (inetSocketAddress.getPort() == p.getPort()) + { + provider = p.getAuthenticationProvider(); + break; + } + } + return provider; + } + + @Override + public Collection getKeyStores() + { + synchronized(_keyStores) + { + return Collections.unmodifiableCollection(_keyStores.values()); + } + } + + @Override + public Collection getTrustStores() + { + synchronized(_trustStores) + { + return Collections.unmodifiableCollection(_trustStores.values()); + } + } + + @Override + public VirtualHostRegistry getVirtualHostRegistry() + { + return _virtualHostRegistry; + } + + @Override + public TaskExecutor getTaskExecutor() + { + return super.getTaskExecutor(); + } + + @Override + protected void changeAttributes(Map attributes) + { + Map convertedAttributes = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + validateAttributes(convertedAttributes); + + super.changeAttributes(convertedAttributes); + } + + private void validateAttributes(Map convertedAttributes) + { + if (convertedAttributes.containsKey(MODEL_VERSION) && !Model.MODEL_VERSION.equals(convertedAttributes.get(MODEL_VERSION))) + { + throw new IllegalConfigurationException("Cannot change the model version"); + } + + if (convertedAttributes.containsKey(STORE_VERSION) + && !new Integer(_brokerStore.getVersion()).equals(convertedAttributes.get(STORE_VERSION))) + { + throw new IllegalConfigurationException("Cannot change the store version"); + } + + String defaultVirtualHost = (String) convertedAttributes.get(DEFAULT_VIRTUAL_HOST); + if (defaultVirtualHost != null) + { + VirtualHost foundHost = findVirtualHostByName(defaultVirtualHost); + if (foundHost == null) + { + throw new IllegalConfigurationException("Virtual host with name " + defaultVirtualHost + + " cannot be set as a default as it does not exist"); + } + } + Long queueFlowControlSize = (Long) convertedAttributes.get(QUEUE_FLOW_CONTROL_SIZE_BYTES); + Long queueFlowControlResumeSize = (Long) convertedAttributes.get(QUEUE_FLOW_CONTROL_RESUME_SIZE_BYTES); + if (queueFlowControlSize != null || queueFlowControlResumeSize != null ) + { + if (queueFlowControlSize == null) + { + queueFlowControlSize = (Long)getAttribute(QUEUE_FLOW_CONTROL_SIZE_BYTES); + } + if (queueFlowControlResumeSize == null) + { + queueFlowControlResumeSize = (Long)getAttribute(QUEUE_FLOW_CONTROL_RESUME_SIZE_BYTES); + } + if (queueFlowControlResumeSize > queueFlowControlSize) + { + throw new IllegalConfigurationException("Flow resume size can't be greater than flow control size"); + } + } + for (String attributeName : POSITIVE_NUMERIC_ATTRIBUTES) + { + Number value = (Number) convertedAttributes.get(attributeName); + if (value != null && value.longValue() < 0) + { + throw new IllegalConfigurationException("Only positive integer value can be specified for the attribute " + + attributeName); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_securityManager.authoriseConfiguringBroker(getName(), Broker.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of broker attributes is denied"); + } + } + + @Override + protected void authoriseCreateChild(Class childClass, Map attributes, + ConfiguredObject... otherParents) throws AccessControlException + { + if (!_securityManager.authoriseConfiguringBroker(String.valueOf(attributes.get(NAME)), childClass, Operation.CREATE)) + { + throw new AccessControlException("Creation of new broker level entity is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + if (!_securityManager.authoriseConfiguringBroker(getName(), Broker.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of broker attributes is denied"); + } + } + + @Override + public boolean isManagementMode() + { + return _brokerOptions.isManagementMode(); + } + + @Override + public Collection getAccessControlProviders() + { + synchronized (_accessControlProviders) + { + return new ArrayList(_accessControlProviders.values()); + } + } + + private void recordPreviouslyUsedPortNumberIfNecessary(Port port, Integer portNumber) + { + //If we haven't previously recorded its original port number, record it now + if(!_stillInUsePortNumbers.containsKey(port)) + { + _stillInUsePortNumbers.put(port, portNumber); + } + } + + private boolean isPreviouslyUsedPortNumber(Port port) + { + return _stillInUsePortNumbers.containsValue(port.getPort()); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ConnectionAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ConnectionAdapter.java new file mode 100644 index 0000000000..e8bacb2712 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ConnectionAdapter.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.model.adapter; + +import java.security.AccessControlException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Connection; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Session; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.stats.StatisticsGatherer; + +final class ConnectionAdapter extends AbstractAdapter implements Connection +{ + private AMQConnectionModel _connection; + + private final Map _sessionAdapters = + new HashMap(); + private final Statistics _statistics; + + public ConnectionAdapter(final AMQConnectionModel conn, TaskExecutor taskExecutor) + { + super(UUIDGenerator.generateRandomUUID(), taskExecutor); + _connection = conn; + _statistics = new ConnectionStatisticsAdapter(conn); + } + + public Collection getSessions() + { + List actualSessions = _connection.getSessionModels(); + + synchronized (_sessionAdapters) + { + Iterator iterator = _sessionAdapters.keySet().iterator(); + while(iterator.hasNext()) + { + AMQSessionModel session = iterator.next(); + if(!actualSessions.contains(session)) + { + SessionAdapter adapter = _sessionAdapters.get(session); + iterator.remove(); + + childRemoved(adapter); // Trigger corresponding ConfigurationChangeListener childRemoved() callback. + } + } + + for(AMQSessionModel session : actualSessions) + { + if(!_sessionAdapters.containsKey(session)) + { + SessionAdapter adapter = new SessionAdapter(session, getTaskExecutor()); + _sessionAdapters.put(session, adapter); + childAdded(adapter); // Trigger corresponding ConfigurationChangeListener childAdded() callback. + } + } + + return new ArrayList(_sessionAdapters.values()); + } + } + + /** + * Retrieve the SessionAdapter instance keyed by the AMQSessionModel from this Connection. + * @param session the AMQSessionModel used to index the SessionAdapter. + * @return the requested SessionAdapter. + */ + SessionAdapter getSessionAdapter(AMQSessionModel session) + { + synchronized (_sessionAdapters) + { + getSessions(); // Call getSessions() first to ensure _sessionAdapters state is up to date with actualSessions. + return _sessionAdapters.get(session); + } + } + + public void delete() + { + try + { + _connection.close(AMQConstant.CONNECTION_FORCED, "Connection closed by external action"); + } + catch(AMQException e) + { + throw new IllegalStateException(e); + } + } + + public String getName() + { + final String remoteAddressString = _connection.getRemoteAddressString(); + return remoteAddressString.replaceAll("/",""); + } + + public String setName(final String currentName, final String desiredName) + throws IllegalStateException, AccessControlException + { + return null; //TODO + } + + public State getActualState() + { + return null; //TODO + } + + public boolean isDurable() + { + return false; //TODO + } + + public void setDurable(final boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + //TODO + } + + public LifetimePolicy getLifetimePolicy() + { + return null; //TODO + } + + public LifetimePolicy setLifetimePolicy(final LifetimePolicy expected, final LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return null; //TODO + } + + public long getTimeToLive() + { + return 0; //TODO + } + + public long setTimeToLive(final long expected, final long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return 0; //TODO + } + + @Override + public Object getAttribute(String name) + { + + if(name.equals(ID)) + { + return getId(); + } + else if (name.equals(NAME)) + { + return getName(); + } + else if(name.equals(CLIENT_ID)) + { + return _connection.getClientId(); + } + else if(name.equals(CLIENT_VERSION)) + { + return _connection.getClientVersion(); + } + else if(name.equals(INCOMING)) + { + return true; + } + else if(name.equals(LOCAL_ADDRESS)) + { + + } + else if(name.equals(PRINCIPAL)) + { + return _connection.getPrincipalAsString(); + } + else if(name.equals(PROPERTIES)) + { + + } + else if(name.equals(REMOTE_ADDRESS)) + { + return _connection.getRemoteAddressString(); + } + else if(name.equals(REMOTE_PROCESS_NAME)) + { + + } + else if(name.equals(REMOTE_PROCESS_PID)) + { + + } + else if(name.equals(SESSION_COUNT_LIMIT)) + { + return _connection.getSessionCountLimit(); + } + else if(name.equals(TRANSPORT)) + { + return String.valueOf(_connection.getTransport()); + } + else if(name.equals(PORT)) + { + return String.valueOf(_connection.getPort()); + } + return super.getAttribute(name); + } + + @Override + public Collection getAttributeNames() + { + final HashSet attrNames = new HashSet(super.getAttributeNames()); + attrNames.addAll(Connection.AVAILABLE_ATTRIBUTES); + return Collections.unmodifiableCollection(attrNames); + } + + public Statistics getStatistics() + { + return _statistics; + } + + @Override + public Collection getChildren(Class clazz) + { + if(clazz == Session.class) + { + return (Collection) getSessions(); + } + else + { + return Collections.emptySet(); + } + } + + @Override + public C addChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + if(childClass == Session.class) + { + throw new IllegalStateException(); + } + else + { + throw new IllegalArgumentException("Cannot create a child of class " + childClass.getSimpleName()); + } + + } + + private class ConnectionStatisticsAdapter extends StatisticsAdapter + { + public ConnectionStatisticsAdapter(StatisticsGatherer applicationRegistry) + { + super(applicationRegistry); + } + + @Override + public Collection getStatisticNames() + { + return Connection.AVAILABLE_STATISTICS; + } + + @Override + public Object getStatistic(String name) + { + if(LAST_IO_TIME.equals(name)) + { + return _connection.getLastIoTime(); + } + else if(SESSION_COUNT.equals(name)) + { + return _connection.getSessionModels().size(); + } + return super.getStatistic(name); + } + } + + @Override + protected boolean setState(State currentState, State desiredState) + { + // TODO: add state management + return false; + } + + @Override + public Object setAttribute(final String name, final Object expected, final Object desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on connection is not supported."); + } + + @Override + public void setAttributes(final Map attributes) throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on connection is not supported."); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ConsumerAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ConsumerAdapter.java new file mode 100644 index 0000000000..696c59783e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ConsumerAdapter.java @@ -0,0 +1,242 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.util.Map; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Consumer; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.subscription.Subscription; + +import java.security.AccessControlException; +import java.util.Collection; +import java.util.Collections; + +public class ConsumerAdapter extends AbstractAdapter implements Consumer +{ + private final Subscription _subscription; + private final QueueAdapter _queue; + private final SessionAdapter _session; + private final ConsumerStatistics _statistics; + + public ConsumerAdapter(final QueueAdapter queueAdapter, final SessionAdapter sessionAdapter, + final Subscription subscription) + { + super(UUIDGenerator.generateConsumerUUID(queueAdapter.getVirtualHost().getName(), + queueAdapter.getName(), + subscription.getSessionModel().getConnectionModel().getRemoteAddressString(), + String.valueOf(subscription.getSessionModel().getChannelId()), + subscription.getConsumerName()), queueAdapter.getTaskExecutor()); + _subscription = subscription; + _queue = queueAdapter; + _session = sessionAdapter; + _statistics = new ConsumerStatistics(); + //TODO + } + + public String getName() + { + return _subscription.getConsumerName(); + } + + public String setName(final String currentName, final String desiredName) + throws IllegalStateException, AccessControlException + { + return null; //TODO + } + + public State getActualState() + { + return null; //TODO + } + + public boolean isDurable() + { + return false; //TODO + } + + public void setDurable(final boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + //TODO + } + + public LifetimePolicy getLifetimePolicy() + { + return null; //TODO + } + + public LifetimePolicy setLifetimePolicy(final LifetimePolicy expected, final LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return null; //TODO + } + + public long getTimeToLive() + { + return 0; //TODO + } + + public long setTimeToLive(final long expected, final long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return 0; //TODO + } + + @Override + public Collection getAttributeNames() + { + return Consumer.AVAILABLE_ATTRIBUTES; + } + + @Override + public Object getAttribute(final String name) + { + if(ID.equals(name)) + { + return getId(); + } + else if(NAME.equals(name)) + { + return getName(); + } + else if(STATE.equals(name)) + { + + } + else if(DURABLE.equals(name)) + { + return false; + } + else if(LIFETIME_POLICY.equals(name)) + { + return LifetimePolicy.AUTO_DELETE; + } + else if(TIME_TO_LIVE.equals(name)) + { + + } + else if(CREATED.equals(name)) + { + + } + else if(UPDATED.equals(name)) + { + + } + else if(DISTRIBUTION_MODE.equals(name)) + { + return _subscription.acquires() ? "MOVE" : "COPY"; + } + else if(SETTLEMENT_MODE.equals(name)) + { + + } + else if(EXCLUSIVE.equals(name)) + { + + } + else if(NO_LOCAL.equals(name)) + { + + } + else if(SELECTOR.equals(name)) + { + + } + return super.getAttribute(name); //TODO + } + + public Statistics getStatistics() + { + return _statistics; + } + + @Override + public Collection getChildren(Class clazz) + { + return Collections.emptySet(); + } + + @Override + public C createChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + throw new IllegalArgumentException(); + } + + private class ConsumerStatistics implements Statistics + { + + public Collection getStatisticNames() + { + return AVAILABLE_STATISTICS; + } + + public Object getStatistic(String name) + { + if(name.equals(BYTES_OUT)) + { + return _subscription.getBytesOut(); + } + else if(name.equals(MESSAGES_OUT)) + { + return _subscription.getMessagesOut(); + } + else if(name.equals(STATE_CHANGED)) + { + + } + else if(name.equals(UNACKNOWLEDGED_BYTES)) + { + return _subscription.getUnacknowledgedBytes(); + } + else if(name.equals(UNACKNOWLEDGED_MESSAGES)) + { + return _subscription.getUnacknowledgedMessages(); + } + return null; // TODO - Implement + } + } + + @Override + protected boolean setState(State currentState, State desiredState) + { + // TODO : Add state management + return false; + } + + @Override + public Object setAttribute(final String name, final Object expected, final Object desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on consumer is not supported."); + } + + @Override + public void setAttributes(final Map attributes) throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on consumer is not supported."); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ExchangeAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ExchangeAdapter.java new file mode 100644 index 0000000000..8efce39ea2 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/ExchangeAdapter.java @@ -0,0 +1,444 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.security.AccessControlException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Publisher; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.util.MapValueConverter; +import org.apache.qpid.server.virtualhost.RequiredExchangeException; +import org.apache.qpid.server.virtualhost.VirtualHost; + +final class ExchangeAdapter extends AbstractAdapter implements Exchange, org.apache.qpid.server.exchange.Exchange.BindingListener +{ + + private final org.apache.qpid.server.exchange.Exchange _exchange; + private final Map _bindingAdapters = + new HashMap(); + private VirtualHostAdapter _vhost; + private final ExchangeStatistics _statistics; + + public ExchangeAdapter(final VirtualHostAdapter virtualHostAdapter, + final org.apache.qpid.server.exchange.Exchange exchange) + { + super(exchange.getId(), virtualHostAdapter.getTaskExecutor()); + _statistics = new ExchangeStatistics(); + _vhost = virtualHostAdapter; + _exchange = exchange; + addParent(org.apache.qpid.server.model.VirtualHost.class, virtualHostAdapter); + + exchange.addBindingListener(this); + populateBindings(); + } + + private void populateBindings() + { + Collection actualBindings = _exchange.getBindings(); + synchronized (_bindingAdapters) + { + for(Binding binding : actualBindings) + { + if(!_bindingAdapters.containsKey(binding)) + { + QueueAdapter queueAdapter = _vhost.getQueueAdapter(binding.getQueue()); + BindingAdapter adapter = new BindingAdapter(binding, this, queueAdapter); + _bindingAdapters.put(binding, adapter); + + queueAdapter.bindingRegistered(binding, adapter); + } + } + } + + } + + public String getExchangeType() + { + return _exchange.getType().getType(); + } + + public Collection getBindings() + { + synchronized (_bindingAdapters) + { + return new ArrayList(_bindingAdapters.values()); + } + + } + + public Collection getPublishers() + { + // TODO + return Collections.emptyList(); + } + + + public org.apache.qpid.server.model.Binding createBinding(Queue queue, + Map attributes) + throws AccessControlException, IllegalStateException + { + attributes = new HashMap(attributes); + String bindingKey = MapValueConverter.getStringAttribute(org.apache.qpid.server.model.Binding.NAME, attributes, ""); + Map bindingArgs = MapValueConverter.getMapAttribute(org.apache.qpid.server.model.Binding.ARGUMENTS, attributes, Collections.emptyMap()); + + attributes.remove(org.apache.qpid.server.model.Binding.NAME); + attributes.remove(org.apache.qpid.server.model.Binding.ARGUMENTS); + + return createBinding(bindingKey, queue, bindingArgs, attributes); + + } + + public org.apache.qpid.server.model.Binding createBinding(String bindingKey, Queue queue, + Map bindingArguments, + Map attributes) + throws AccessControlException, IllegalStateException + { + VirtualHost virtualHost = _vhost.getVirtualHost(); + + + AMQQueue amqQueue = ((QueueAdapter)queue).getAMQQueue(); + + try + { + if(!_exchange.addBinding(bindingKey, amqQueue, bindingArguments)) + { + Binding oldBinding = _exchange.getBinding(bindingKey, amqQueue, bindingArguments); + + Map oldArgs = oldBinding.getArguments(); + if((oldArgs == null && !bindingArguments.isEmpty()) || (oldArgs != null && !oldArgs.equals(bindingArguments))) + { + _exchange.replaceBinding(oldBinding.getId(), bindingKey, amqQueue, bindingArguments); + } + } + Binding binding = _exchange.getBinding(bindingKey, amqQueue, bindingArguments); + + synchronized (_bindingAdapters) + { + return binding == null ? null : _bindingAdapters.get(binding); + } + } + catch(AMQSecurityException e) + { + throw new AccessControlException(e.toString()); + } + catch(AMQInternalException e) + { + throw new IllegalStateException(e); + } + } + + public void delete() + { + try + { + _vhost.getVirtualHost().removeExchange(_exchange, true); + } + catch(RequiredExchangeException e) + { + throw new UnsupportedOperationException("'" + getName() + "' is a reserved exchange and can't be deleted"); + } + catch(AMQException e) + { + throw new IllegalStateException(e); + } + } + + public String getName() + { + return _exchange.getName(); + } + + public String setName(final String currentName, final String desiredName) + throws IllegalStateException, AccessControlException + { + return null; //TODO + } + + public State getActualState() + { + return null; //TODO + } + + public boolean isDurable() + { + return _exchange.isDurable(); + } + + public void setDurable(final boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + //TODO + } + + public LifetimePolicy getLifetimePolicy() + { + return _exchange.isAutoDelete() ? LifetimePolicy.AUTO_DELETE : LifetimePolicy.PERMANENT; + } + + public LifetimePolicy setLifetimePolicy(final LifetimePolicy expected, final LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return null; //TODO + } + + public long getTimeToLive() + { + return 0; //TODO + } + + public long setTimeToLive(final long expected, final long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return 0; //TODO + } + + public Statistics getStatistics() + { + return _statistics; + } + + @Override + public Collection getChildren(Class clazz) + { + if(clazz == org.apache.qpid.server.model.Binding.class) + { + return (Collection) getBindings(); + } + else + { + return Collections.emptySet(); + } + } + + @Override + public C addChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + if(childClass == org.apache.qpid.server.model.Binding.class) + { + if(otherParents != null && otherParents.length == 1 && otherParents[0] instanceof Queue) + { + Queue queue = (Queue) otherParents[0]; + if(queue.getParent(org.apache.qpid.server.model.VirtualHost.class) == getParent(org.apache.qpid.server.model.VirtualHost.class)) + { + return (C) createBinding(queue, attributes); + } + else + { + throw new IllegalArgumentException("Queue and Exchange parents of a binding must be on same virtual host"); + } + } + else + { + throw new IllegalArgumentException("Other parent must be a queue"); + } + } + else + { + throw new IllegalArgumentException(); + } + } + + public void bindingAdded(org.apache.qpid.server.exchange.Exchange exchange, Binding binding) + { + BindingAdapter adapter = null; + synchronized (_bindingAdapters) + { + if(!_bindingAdapters.containsKey(binding)) + { + QueueAdapter queueAdapter = _vhost.getQueueAdapter(binding.getQueue()); + adapter = new BindingAdapter(binding, this, queueAdapter); + _bindingAdapters.put(binding,adapter); + queueAdapter.bindingRegistered(binding,adapter); + } + } + if(adapter != null) + { + childAdded(adapter); + } + } + + public void bindingRemoved(org.apache.qpid.server.exchange.Exchange exchange, Binding binding) + { + BindingAdapter adapter = null; + synchronized (_bindingAdapters) + { + adapter = _bindingAdapters.remove(binding); + } + if(adapter != null) + { + QueueAdapter queueAdapter = _vhost.getQueueAdapter(binding.getQueue()); + if(queueAdapter != null) + { + queueAdapter.bindingUnregistered(binding); + childRemoved(adapter); + } + } + } + + org.apache.qpid.server.exchange.Exchange getExchange() + { + return _exchange; + } + + @Override + public Object getAttribute(String name) + { + if(ID.equals(name)) + { + return getId(); + } + else if(NAME.equals(name)) + { + return getName(); + } + else if(STATE.equals(name)) + { + return State.ACTIVE; + } + else if(DURABLE.equals(name)) + { + return isDurable(); + } + else if(LIFETIME_POLICY.equals(name)) + { + return _exchange.isAutoDelete() ? LifetimePolicy.AUTO_DELETE : LifetimePolicy.PERMANENT; + } + else if(TIME_TO_LIVE.equals(name)) + { + + } + else if(CREATED.equals(name)) + { + + } + else if(UPDATED.equals(name)) + { + + } + else if(ALTERNATE_EXCHANGE.equals(name)) + { + return _exchange.getAlternateExchange(); + } + else if(TYPE.equals(name)) + { + return _exchange.getTypeName(); + } + return super.getAttribute(name); + } + + @Override + public Collection getAttributeNames() + { + return AVAILABLE_ATTRIBUTES; + } + + @Override + protected boolean setState(State currentState, State desiredState) + { + if (desiredState == State.DELETED) + { + delete(); + return true; + } + return false; + } + + @Override + protected void changeAttributes(Map attributes) + { + throw new UnsupportedOperationException("Changing attributes on exchange is not supported."); + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_vhost.getSecurityManager().authoriseUpdate(_exchange)) + { + throw new AccessControlException("Setting of exchange attribute is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + if (!_vhost.getSecurityManager().authoriseUpdate(_exchange)) + { + throw new AccessControlException("Setting of exchange attributes is denied"); + } + } + + private class ExchangeStatistics implements Statistics + { + + public Collection getStatisticNames() + { + return AVAILABLE_STATISTICS; + } + + public Object getStatistic(String name) + { + if(BINDING_COUNT.equals(name)) + { + return _exchange.getBindingCount(); + } + else if(BYTES_DROPPED.equals(name)) + { + return _exchange.getByteDrops(); + } + else if(BYTES_IN.equals(name)) + { + return _exchange.getByteReceives(); + } + else if(MESSAGES_DROPPED.equals(name)) + { + return _exchange.getMsgDrops(); + } + else if(MESSAGES_IN.equals(name)) + { + return _exchange.getMsgReceives(); + } + else if(PRODUCER_COUNT.equals(name)) + { + + } + else if(STATE_CHANGED.equals(name)) + { + + } + return null; // TODO - Implement + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProvider.java new file mode 100644 index 0000000000..f7560a0dfa --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProvider.java @@ -0,0 +1,592 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.model.adapter; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.security.AccessControlException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.util.MapValueConverter; +import org.codehaus.jackson.JsonParser; +import org.codehaus.jackson.JsonProcessingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; +import org.codehaus.jackson.type.TypeReference; + +public class FileSystemPreferencesProvider extends AbstractAdapter implements PreferencesProvider +{ + private static final Logger LOGGER = Logger.getLogger(FileSystemPreferencesProvider.class); + public static String PATH = "path"; + public static final String PROVIDER_TYPE = "FileSystemPreferences"; + + // TODO: use resolver to resolve path from + // '${qpid.work_dir}/preferences/${authenticationProviderName}' + @SuppressWarnings("serial") + private static final Map DEFAULTS = Collections.unmodifiableMap(new HashMap() + {{ + put(PATH, System.getProperty("user.home") + File.separator + ".qpid" + File.separator + "preferences.json"); + put(TYPE, FileSystemPreferencesProvider.class.getSimpleName()); + }}); + + @SuppressWarnings("serial") + private static final Map ATTRIBUTE_TYPES = Collections.unmodifiableMap(new HashMap() + {{ + put(NAME, String.class); + put(PATH, String.class); + put(TYPE, String.class); + }}); + + @SuppressWarnings("serial") + private static Collection AVAILABLE_ATTRIBUTES = Collections.unmodifiableList(new ArrayList( + PreferencesProvider.AVAILABLE_ATTRIBUTES) + {{ + add(PATH); + }}); + + private final AuthenticationProvider _authenticationProvider; + private AtomicReference _state; + + private final ObjectMapper _objectMapper; + private final Map> _preferences; + private File _preferencesLocation; + private FileLock _fileLock; + + protected FileSystemPreferencesProvider(UUID id, Map attributes, AuthenticationProvider authenticationProvider, TaskExecutor taskExecutor) + { + super(id, DEFAULTS, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES), taskExecutor); + State state = MapValueConverter.getEnumAttribute(State.class, STATE, attributes, State.INITIALISING); + _state = new AtomicReference(state); + addParent(AuthenticationProvider.class, authenticationProvider); + _authenticationProvider = authenticationProvider; + _objectMapper = new ObjectMapper(); + _objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + _objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true); + _preferences = new TreeMap>(); + _preferencesLocation = new File(MapValueConverter.getStringAttribute(PATH, attributes)); + _preferences.putAll(load(_objectMapper, _preferencesLocation)); + } + + @Override + public Collection getAttributeNames() + { + return AVAILABLE_ATTRIBUTES; + } + + @Override + public String getName() + { + return (String) getAttribute(AuthenticationProvider.NAME); + } + + @Override + public String setName(String currentName, String desiredName) throws IllegalStateException, AccessControlException + { + throw new UnsupportedOperationException(); + } + + @Override + public State getActualState() + { + return _state.get(); + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, LifetimePolicy desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new UnsupportedOperationException(); + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public Collection getChildren(Class clazz) + { + return Collections.emptySet(); + } + + @Override + public Object getAttribute(String name) + { + if (CREATED.equals(name)) + { + // TODO + } + else if (DURABLE.equals(name)) + { + return true; + } + else if (ID.equals(name)) + { + return getId(); + } + else if (LIFETIME_POLICY.equals(name)) + { + return LifetimePolicy.PERMANENT; + } + else if (STATE.equals(name)) + { + return getActualState(); + } + else if (TIME_TO_LIVE.equals(name)) + { + // TODO + } + else if (UPDATED.equals(name)) + { + // TODO + } + return super.getAttribute(name); + } + + @Override + public boolean setState(State currentState, State desiredState) throws IllegalStateTransitionException, + AccessControlException + { + State state = _state.get(); + if (desiredState == State.DELETED) + { + if ((state == State.INITIALISING || state == State.ACTIVE || state == State.STOPPED || state == State.QUIESCED || state == State.ERRORED) + && _state.compareAndSet(state, State.DELETED)) + { + try + { + close(); + } + finally + { + _preferencesLocation.delete(); + _authenticationProvider.setPreferencesProvider(null); + } + return true; + } + else + { + throw new IllegalStateException("Cannot delete preferences provider in state: " + state); + } + } + else if (desiredState == State.ACTIVE) + { + if ((state == State.INITIALISING || state == State.QUIESCED || state == State.STOPPED) + && _state.compareAndSet(state, State.ACTIVE)) + { + try + { + getFileLock(); + Map> preferences = load(_objectMapper, _preferencesLocation); + setPreferences(preferences); + return true; + } + catch (Exception e) + { + _state.compareAndSet(State.ACTIVE, State.ERRORED); + Broker broker = getAuthenticationProvider().getParent(Broker.class); + if (broker != null && broker.isManagementMode()) + { + LOGGER.warn("Failed to activate preferences provider: " + getName(), e); + } + else + { + throw new RuntimeException(e); + } + } + } + else + { + throw new IllegalStateException("Cannot activate preferences provider in state: " + state); + } + } + else if (desiredState == State.QUIESCED) + { + if (state == State.INITIALISING && _state.compareAndSet(state, State.QUIESCED)) + { + close(); + return true; + } + } + else if (desiredState == State.STOPPED) + { + if (_state.compareAndSet(state, State.STOPPED)) + { + close(); + return true; + } + else + { + throw new IllegalStateException("Cannot stop authentication preferences in state: " + state); + } + } + + return false; + } + + @Override + public Map getPreferences(String userId) + { + Map userPreferences = null; + synchronized (_preferences) + { + userPreferences = _preferences.get(userId); + } + if (userPreferences != null) + { + return new HashMap(userPreferences); + } + return Collections.emptyMap(); + } + + @Override + public Map setPreferences(String userId, Map preferences) + { + Map userPreferences = null; + synchronized (_preferences) + { + userPreferences = _preferences.get(userId); + if (userPreferences == null) + { + userPreferences = new HashMap(preferences); + _preferences.put(userId, userPreferences); + } + else + { + userPreferences.putAll(preferences); + } + savePreferences(); + } + return userPreferences; + } + + @Override + public Map deletePreferences(String userId) + { + Map userPreferences = null; + synchronized (_preferences) + { + if (_preferences.containsKey(userId)) + { + userPreferences = _preferences.remove(userId); + savePreferences(); + } + } + return userPreferences; + } + + @Override + public Set listUserIDs() + { + synchronized (_preferences) + { + return Collections.unmodifiableSet(_preferences.keySet()); + } + } + + public AuthenticationProvider getAuthenticationProvider() + { + return _authenticationProvider; + } + + @Override + protected void changeAttributes(Map attributes) + { + Map effectiveAttributes = MapValueConverter.convert(super.generateEffectiveAttributes(attributes), + ATTRIBUTE_TYPES); + validateAttributes(effectiveAttributes); + String effectivePath = (String) effectiveAttributes.get(PATH); + String currentPath = (String) getAttribute(PATH); + Map> newPreferences = null; + File storeFile = new File(effectivePath); + if (!effectivePath.equals(currentPath)) + { + if (!storeFile.exists()) + { + throw new IllegalConfigurationException("Path to preferences file does not exist!"); + } + newPreferences = load(_objectMapper, storeFile); + } + super.changeAttributes(attributes); + + if (newPreferences != null) + { + setPreferences(newPreferences); + _preferencesLocation = storeFile; + } + + // if provider was previously in ERRORED state then set its state to + // ACTIVE + _state.compareAndSet(State.ERRORED, State.ACTIVE); + } + + private void setPreferences(Map> preferences) + { + synchronized (_preferences) + { + _preferences.clear(); + _preferences.putAll(preferences); + } + } + + private void validateAttributes(Map attributes) + { + super.validateChangeAttributes(attributes); + + String newName = (String) attributes.get(NAME); + String currentName = getName(); + if (!currentName.equals(newName)) + { + throw new IllegalConfigurationException("Changing the name of preferences provider is not supported"); + } + String newType = (String) attributes.get(TYPE); + String currentType = (String) getAttribute(TYPE); + if (!currentType.equals(newType)) + { + throw new IllegalConfigurationException("Changing the type of preferences provider is not supported"); + } + String path = (String) attributes.get(PATH); + if (path == null || path.equals("") || !(path instanceof String)) + { + throw new IllegalConfigurationException("Path to preferences file is not specified"); + } + } + + public File createStoreIfNotExist() + { + String path = (String)getAttribute(PATH); + File preferencesLocation = new File(path); + if (!preferencesLocation.exists()) + { + File parent = preferencesLocation.getParentFile(); + if (!parent.exists() && !parent.mkdirs()) + { + throw new IllegalConfigurationException("Cannot store preferences at " + path); + } + try + { + preferencesLocation.createNewFile(); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot store preferences at " + path); + } + } + return preferencesLocation; + } + + private Map> load(ObjectMapper mapper, File file) + { + if (!file.exists() || file.length() == 0) + { + return Collections.emptyMap(); + } + + try + { + return mapper.readValue(file, new TypeReference>>() + { + }); + } + catch (JsonProcessingException e) + { + throw new IllegalConfigurationException("Cannot parse json", e); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot read json", e); + } + } + + private void savePreferences() + { + save(_objectMapper, _preferencesLocation, _preferences); + } + + private void save(ObjectMapper mapper, File file, Map> preferences) + { + try + { + RandomAccessFile raf = new RandomAccessFile(file, "rw"); + try + { + FileChannel channel = raf.getChannel(); + try + { + FileLock lock = null; + try + { + lock = channel.tryLock(); + if (lock == null) + { + throw new IllegalConfigurationException("Cannot aquire exclusive lock on preferences file for " + + getName()); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + mapper.writeValue(baos, preferences); + channel.write(ByteBuffer.wrap(baos.toByteArray())); + } + catch (OverlappingFileLockException e) + { + throw new IllegalConfigurationException("Cannot aquire exclusive lock on preferences file for " + + getName(), e); + } + finally + { + if (lock != null) + { + lock.release(); + } + } + } + finally + { + channel.close(); + } + } + finally + { + raf.close(); + } + } + catch (FileNotFoundException e) + { + throw new IllegalConfigurationException("Cannot find preferences file for " + getName(), e); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot store preferences file for " + getName(), e); + } + } + + private void getFileLock() throws IOException, AMQStoreException + { + File lockFile = new File(getLockFileName()); + lockFile.createNewFile(); + + FileOutputStream out = new FileOutputStream(lockFile); + FileChannel channel = out.getChannel(); + try + { + _fileLock = channel.tryLock(); + } + catch(OverlappingFileLockException e) + { + _fileLock = null; + } + if(_fileLock == null) + { + throw new AMQStoreException("Cannot get lock on file " + lockFile.getAbsolutePath() + " is another instance running?"); + } + lockFile.deleteOnExit(); + } + + private String getLockFileName() + { + return _preferencesLocation.getAbsolutePath() + ".lck"; + } + + public void close() + { + try + { + releaseFileLock(); + } + catch(IOException e) + { + LOGGER.error("Cannot close file system preferences provider", e); + } + finally + { + new File(getLockFileName()).delete(); + _fileLock = null; + _preferences.clear(); + } + } + + private void releaseFileLock() throws IOException + { + _fileLock.release(); + _fileLock.channel().close(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderFactory.java new file mode 100644 index 0000000000..32ee910973 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderFactory.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.model.adapter; + +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.plugin.PreferencesProviderFactory; + +public class FileSystemPreferencesProviderFactory implements PreferencesProviderFactory +{ + + @Override + public String getType() + { + return FileSystemPreferencesProvider.PROVIDER_TYPE; + } + + @Override + public PreferencesProvider createInstance(UUID id, Map attributes, + AuthenticationProvider authenticationProvider) + { + Broker broker = authenticationProvider.getParent(Broker.class); + FileSystemPreferencesProvider provider = new FileSystemPreferencesProvider(id, attributes, authenticationProvider, broker.getTaskExecutor()); + + // create store if such does not exist + provider.createStoreIfNotExist(); + return provider; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java new file mode 100644 index 0000000000..9323606c83 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderAdapter.java @@ -0,0 +1,710 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.security.AccessControlException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Group; +import org.apache.qpid.server.model.GroupMember; +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.security.group.GroupManager; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.util.MapValueConverter; + +public class GroupProviderAdapter extends AbstractAdapter implements + GroupProvider +{ + private static Logger LOGGER = Logger.getLogger(GroupProviderAdapter.class); + + private final GroupManager _groupManager; + private final Broker _broker; + private Collection _supportedAttributes; + private AtomicReference _state; + + public GroupProviderAdapter(UUID id, Broker broker, GroupManager groupManager, Map attributes, Collection attributeNames) + { + super(id, null, null, broker.getTaskExecutor()); + + if (groupManager == null) + { + throw new IllegalArgumentException("GroupManager must not be null"); + } + _groupManager = groupManager; + _broker = broker; + _supportedAttributes = createSupportedAttributes(attributeNames); + State state = MapValueConverter.getEnumAttribute(State.class, STATE, attributes, State.INITIALISING); + _state = new AtomicReference(state); + addParent(Broker.class, broker); + + // set attributes now after all attribute names are known + if (attributes != null) + { + for (String name : _supportedAttributes) + { + if (attributes.containsKey(name)) + { + changeAttribute(name, null, attributes.get(name)); + } + } + } + } + + protected Collection createSupportedAttributes(Collection factoryAttributes) + { + List attributesNames = new ArrayList(AVAILABLE_ATTRIBUTES); + if (factoryAttributes != null) + { + attributesNames.addAll(factoryAttributes); + } + + return Collections.unmodifiableCollection(attributesNames); + } + + @Override + public String getName() + { + return (String)getAttribute(NAME); + } + + @Override + public String setName(String currentName, String desiredName) + throws IllegalStateException, AccessControlException + { + return null; + } + + @Override + public State getActualState() + { + return _state.get(); + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, + LifetimePolicy desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + return null; + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + return 0; + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public Collection getAttributeNames() + { + return _supportedAttributes; + } + + @Override + public Object getAttribute(String name) + { + if (CREATED.equals(name)) + { + // TODO + } + else if (DURABLE.equals(name)) + { + return true; + } + else if (ID.equals(name)) + { + return getId(); + } + else if (LIFETIME_POLICY.equals(name)) + { + return LifetimePolicy.PERMANENT; + } + else if (STATE.equals(name)) + { + return getActualState(); + } + else if (TIME_TO_LIVE.equals(name)) + { + // TODO + } + else if (UPDATED.equals(name)) + { + // TODO + } + return super.getAttribute(name); + } + + @Override + public C addChild(Class childClass, + Map attributes, ConfiguredObject... otherParents) + { + if (childClass == Group.class) + { + String groupName = (String) attributes.get(Group.NAME); + + if (getSecurityManager().authoriseGroupOperation(Operation.CREATE, groupName)) + { + _groupManager.createGroup(groupName); + return (C) new GroupAdapter(groupName, getTaskExecutor()); + } + else + { + throw new AccessControlException("Do not have permission" + + " to create new group"); + } + } + + throw new IllegalArgumentException( + "This group provider does not support creating children of type: " + + childClass); + } + + @SuppressWarnings("unchecked") + @Override + public Collection getChildren(Class clazz) + { + if (clazz == Group.class) + { + Set groups = _groupManager.getGroupPrincipals(); + Collection principals = new ArrayList(groups.size()); + for (Principal group : groups) + { + principals.add(new GroupAdapter(group.getName(), getTaskExecutor())); + } + return (Collection) Collections + .unmodifiableCollection(principals); + } + else + { + return null; + } + } + + public GroupManager getGroupManager() + { + return _groupManager; + } + + private SecurityManager getSecurityManager() + { + return _broker.getSecurityManager(); + } + + @Override + protected boolean setState(State currentState, State desiredState) + { + State state = _state.get(); + if (desiredState == State.ACTIVE) + { + if ((state == State.INITIALISING || state == State.QUIESCED || state == State.STOPPED) + && _state.compareAndSet(state, State.ACTIVE)) + { + try + { + _groupManager.open(); + return true; + } + catch(RuntimeException e) + { + _state.compareAndSet(State.ACTIVE, State.ERRORED); + if (_broker.isManagementMode()) + { + LOGGER.warn("Failed to activate group provider: " + getName(), e); + } + else + { + throw e; + } + } + } + else + { + throw new IllegalStateException("Cannot activate group provider in state: " + state); + } + } + else if (desiredState == State.STOPPED) + { + if (_state.compareAndSet(state, State.STOPPED)) + { + _groupManager.close(); + return true; + } + else + { + throw new IllegalStateException("Cannot stop group provider in state: " + state); + } + } + else if (desiredState == State.DELETED) + { + if ((state == State.INITIALISING || state == State.ACTIVE || state == State.STOPPED || state == State.QUIESCED || state == State.ERRORED) + && _state.compareAndSet(state, State.DELETED)) + { + _groupManager.close(); + _groupManager.onDelete(); + return true; + } + else + { + throw new IllegalStateException("Cannot delete group provider in state: " + state); + } + } + else if (desiredState == State.QUIESCED) + { + if (state == State.INITIALISING && _state.compareAndSet(state, State.QUIESCED)) + { + return true; + } + } + return false; + } + + public Set getGroupPrincipalsForUser(String username) + { + return _groupManager.getGroupPrincipalsForUser(username); + } + + @Override + protected void childAdded(ConfiguredObject child) + { + // no-op, prevent storing groups in the broker store + } + + @Override + protected void childRemoved(ConfiguredObject child) + { + // no-op, as per above, groups are not in the store + } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), GroupProvider.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of groups provider is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), GroupProvider.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of group provider attributes is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), GroupProvider.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of group provider attributes is denied"); + } + } + + @Override + protected void changeAttributes(Map attributes) + { + throw new UnsupportedOperationException("Changing attributes on group providers is not supported."); + } + + private class GroupAdapter extends AbstractAdapter implements Group + { + private final String _group; + + public GroupAdapter(String group, TaskExecutor taskExecutor) + { + super(UUIDGenerator.generateGroupUUID(GroupProviderAdapter.this.getName(), group), taskExecutor); + _group = group; + + } + + @Override + public String getName() + { + return _group; + } + + @Override + public String setName(String currentName, String desiredName) + throws IllegalStateException, AccessControlException + { + throw new IllegalStateException("Names cannot be updated"); + } + + @Override + public State getActualState() + { + return State.ACTIVE; + } + + @Override + public boolean isDurable() + { + return true; + } + + @Override + public void setDurable(boolean durable) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new IllegalStateException("Durability cannot be updated"); + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, + LifetimePolicy desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new IllegalStateException("LifetimePolicy cannot be updated"); + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new IllegalStateException("ttl cannot be updated"); + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public Collection getChildren( + Class clazz) + { + if (clazz == GroupMember.class) + { + Set usersInGroup = _groupManager + .getUserPrincipalsForGroup(_group); + Collection members = new ArrayList(); + for (Principal principal : usersInGroup) + { + members.add(new GroupMemberAdapter(principal.getName(), getTaskExecutor())); + } + return (Collection) Collections + .unmodifiableCollection(members); + } + else + { + return null; + } + + } + + @Override + public C addChild(Class childClass, + Map attributes, + ConfiguredObject... otherParents) + { + if (childClass == GroupMember.class) + { + String memberName = (String) attributes.get(GroupMember.NAME); + + if (getSecurityManager().authoriseGroupOperation(Operation.UPDATE, _group)) + { + _groupManager.addUserToGroup(memberName, _group); + return (C) new GroupMemberAdapter(memberName, getTaskExecutor()); + } + else + { + throw new AccessControlException("Do not have permission" + + " to add new group member"); + } + } + + throw new IllegalArgumentException( + "This group provider does not support creating children of type: " + + childClass); + } + + @Override + public Collection getAttributeNames() + { + return Group.AVAILABLE_ATTRIBUTES; + } + + @Override + public Object getAttribute(String name) + { + if (ID.equals(name)) + { + return getId(); + } + else if (NAME.equals(name)) + { + return getName(); + } + return super.getAttribute(name); + } + + @Override + protected boolean setState(State currentState, State desiredState) + throws IllegalStateTransitionException, AccessControlException + { + if (desiredState == State.DELETED) + { + if (getSecurityManager().authoriseGroupOperation(Operation.DELETE, _group)) + { + _groupManager.removeGroup(_group); + return true; + } + else + { + throw new AccessControlException("Do not have permission to delete group"); + } + } + + return false; + } + + @Override + public Object setAttribute(final String name, final Object expected, final Object desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on group is not supported."); + } + + @Override + public void setAttributes(final Map attributes) throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on group is not supported."); + } + + private class GroupMemberAdapter extends AbstractAdapter implements + GroupMember + { + private String _memberName; + + public GroupMemberAdapter(String memberName, TaskExecutor taskExecutor) + { + super(UUIDGenerator.generateGroupMemberUUID(GroupProviderAdapter.this.getName(), _group, memberName), taskExecutor); + _memberName = memberName; + } + + @Override + public Collection getAttributeNames() + { + return GroupMember.AVAILABLE_ATTRIBUTES; + } + + @Override + public Object getAttribute(String name) + { + if (ID.equals(name)) + { + return getId(); + } + else if (NAME.equals(name)) + { + return getName(); + } + return super.getAttribute(name); + } + + @Override + public String getName() + { + return _memberName; + } + + @Override + public String setName(String currentName, String desiredName) + throws IllegalStateException, AccessControlException + { + return null; + } + + @Override + public State getActualState() + { + return null; + } + + @Override + public boolean isDurable() + { + return false; + } + + @Override + public void setDurable(boolean durable) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return null; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, + LifetimePolicy desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + return null; + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + return 0; + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public Collection getChildren( + Class clazz) + { + return null; + } + + @Override + public C createChild( + Class childClass, Map attributes, + ConfiguredObject... otherParents) + { + return null; + } + + @Override + protected boolean setState(State currentState, State desiredState) + throws IllegalStateTransitionException, + AccessControlException + { + if (desiredState == State.DELETED) + { + if (getSecurityManager().authoriseGroupOperation(Operation.UPDATE, _group)) + { + _groupManager.removeUserFromGroup(_memberName, _group); + return true; + } + else + { + throw new AccessControlException("Do not have permission to remove group member"); + } + } + return false; + } + + @Override + public Object setAttribute(final String name, final Object expected, final Object desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on group member is not supported."); + } + + @Override + public void setAttributes(final Map attributes) throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on group member is not supported."); + } + } + } + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderFactory.java new file mode 100644 index 0000000000..1d3ccd81b3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/GroupProviderFactory.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.model.adapter; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.plugin.GroupManagerFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.security.group.GroupManager; + +public class GroupProviderFactory +{ + private final Map _factories; + private Collection _supportedGroupProviders; + + public GroupProviderFactory(QpidServiceLoader groupManagerFactoryServiceLoader) + { + Iterable factories = groupManagerFactoryServiceLoader.instancesOf(GroupManagerFactory.class); + + Map registeredGroupProviderFactories = new HashMap(); + for (GroupManagerFactory factory : factories) + { + GroupManagerFactory existingFactory = registeredGroupProviderFactories.put(factory.getType(), factory); + if (existingFactory != null) + { + throw new IllegalConfigurationException("Group provider factory of the same type '" + factory.getType() + + "' is already registered using class '" + existingFactory.getClass().getName() + + "', can not register class '" + factory.getClass().getName() + "'"); + } + } + _factories = registeredGroupProviderFactories; + _supportedGroupProviders = Collections.unmodifiableCollection(registeredGroupProviderFactories.keySet()); + } + + /** + * Creates {@link GroupProvider} for given ID, {@link Broker} and attributes. + *

+ * The configured {@link GroupManagerFactory}'s are used to try to create the {@link GroupProvider}. The first non-null + * instance is returned. The factories are used in non-deterministic order. + */ + public GroupProvider create(UUID id, Broker broker, Map attributes) + { + GroupProviderAdapter authenticationProvider = createGroupProvider(id, broker, attributes); + authenticationProvider.getGroupManager().onCreate(); + return authenticationProvider; + } + + /** + * Recovers {@link GroupProvider} with given ID, {@link Broker} and attributes. + *

+ * The configured {@link GroupManagerFactory}'s are used to try to create the {@link GroupProvider}. The first non-null + * instance is returned. The factories are used in non-deterministic order. + */ + public GroupProvider recover(UUID id, Broker broker, Map attributes) + { + return createGroupProvider(id, broker, attributes); + } + + public Collection getSupportedGroupProviders() + { + return _supportedGroupProviders; + } + + private GroupProviderAdapter createGroupProvider(UUID id, Broker broker, Map attributes) + { + for (GroupManagerFactory factory : _factories.values()) + { + GroupManager manager = factory.createInstance(attributes); + if (manager != null) + { + verifyGroupManager(manager, broker); + return new GroupProviderAdapter(id, broker, manager, attributes,factory.getAttributeNames()); + } + } + throw new IllegalConfigurationException("No group provider factory found for configuration attributes " + attributes); + } + + private void verifyGroupManager(GroupManager manager, Broker broker) + { + Collection groupProviders = broker.getGroupProviders(); + for (GroupProvider groupProvider : groupProviders) + { + if (groupProvider instanceof GroupProviderAdapter) + { + GroupManager providerManager = ((GroupProviderAdapter) groupProvider).getGroupManager(); + if (manager.equals(providerManager)) + { + throw new IllegalConfigurationException("A group provider with the same settings already exists"); + } + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/KeyStoreAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/KeyStoreAdapter.java new file mode 100644 index 0000000000..1101232c96 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/KeyStoreAdapter.java @@ -0,0 +1,254 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.security.AccessControlException; +import java.security.GeneralSecurityException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.IntegrityViolationException; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.util.MapValueConverter; +import org.apache.qpid.transport.network.security.ssl.QpidClientX509KeyManager; +import org.apache.qpid.transport.network.security.ssl.SSLUtil; + +public class KeyStoreAdapter extends AbstractKeyStoreAdapter implements KeyStore +{ + @SuppressWarnings("serial") + public static final Map ATTRIBUTE_TYPES = Collections.unmodifiableMap(new HashMap(){{ + put(NAME, String.class); + put(PATH, String.class); + put(PASSWORD, String.class); + put(TYPE, String.class); + put(CERTIFICATE_ALIAS, String.class); + put(KEY_MANAGER_FACTORY_ALGORITHM, String.class); + }}); + + @SuppressWarnings("serial") + public static final Map DEFAULTS = Collections.unmodifiableMap(new HashMap(){{ + put(KeyStore.TYPE, DEFAULT_KEYSTORE_TYPE); + put(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()); + }}); + + private Broker _broker; + + public KeyStoreAdapter(UUID id, Broker broker, Map attributes) + { + super(id, broker, DEFAULTS, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES)); + _broker = broker; + + String keyStorePath = (String)getAttribute(KeyStore.PATH); + String keyStorePassword = getPassword(); + String keyStoreType = (String)getAttribute(KeyStore.TYPE); + String keyManagerFactoryAlgorithm = (String)getAttribute(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM); + String certAlias = (String)getAttribute(KeyStore.CERTIFICATE_ALIAS); + + validateKeyStoreAttributes(keyStoreType, keyStorePath, keyStorePassword, + certAlias, keyManagerFactoryAlgorithm); + } + + @Override + public Collection getAttributeNames() + { + return AVAILABLE_ATTRIBUTES; + } + + @Override + protected boolean setState(State currentState, State desiredState) + { + if(desiredState == State.DELETED) + { + // verify that it is not in use + String storeName = getName(); + + Collection ports = new ArrayList(_broker.getPorts()); + for (Port port : ports) + { + if (storeName.equals(port.getAttribute(Port.KEY_STORE))) + { + throw new IntegrityViolationException("Key store '" + storeName + "' can't be deleted as it is in use by a port:" + port.getName()); + } + } + + return true; + } + + return false; + } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), KeyStore.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of key store is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + authoriseSetAttribute(); + } + + @Override + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + authoriseSetAttribute(); + } + + private void authoriseSetAttribute() + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), KeyStore.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting key store attributes is denied"); + } + } + + @Override + protected void changeAttributes(Map attributes) + { + Map changedValues = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + if(changedValues.containsKey(KeyStore.NAME)) + { + String newName = (String) changedValues.get(KeyStore.NAME); + if(!getName().equals(newName)) + { + throw new IllegalConfigurationException("Changing the key store name is not allowed"); + } + } + + Map merged = generateEffectiveAttributes(changedValues); + + String keyStorePath = (String)merged.get(KeyStore.PATH); + String keyStorePassword = (String) merged.get(KeyStore.PASSWORD); + String keyStoreType = (String)merged.get(KeyStore.TYPE); + String keyManagerFactoryAlgorithm = (String)merged.get(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM); + String certAlias = (String)merged.get(KeyStore.CERTIFICATE_ALIAS); + + validateKeyStoreAttributes(keyStoreType, keyStorePath, keyStorePassword, + certAlias, keyManagerFactoryAlgorithm); + + super.changeAttributes(changedValues); + } + + private void validateKeyStoreAttributes(String type, String keyStorePath, + String keyStorePassword, String alias, + String keyManagerFactoryAlgorithm) + { + java.security.KeyStore keyStore = null; + try + { + keyStore = SSLUtil.getInitializedKeyStore(keyStorePath, keyStorePassword, type); + } + catch (Exception e) + { + throw new IllegalConfigurationException("Cannot instantiate key store at " + keyStorePath, e); + } + + if (alias != null) + { + Certificate cert = null; + try + { + cert = keyStore.getCertificate(alias); + } + catch (KeyStoreException e) + { + // key store should be initialized above + throw new RuntimeException("Key store has not been initialized", e); + } + if (cert == null) + { + throw new IllegalConfigurationException("Cannot find a certificate with alias " + alias + + "in key store : " + keyStorePath); + } + } + + try + { + KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm); + } + catch (NoSuchAlgorithmException e) + { + throw new IllegalConfigurationException("Unknown keyManagerFactoryAlgorithm: " + + keyManagerFactoryAlgorithm); + } + } + + public KeyManager[] getKeyManagers() throws GeneralSecurityException + { + String keyStorePath = (String)getAttribute(KeyStore.PATH); + String keyStorePassword = getPassword(); + String keyStoreType = (String)getAttribute(KeyStore.TYPE); + String keyManagerFactoryAlgorithm = (String)getAttribute(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM); + String certAlias = (String)getAttribute(KeyStore.CERTIFICATE_ALIAS); + + try + { + if (certAlias != null) + { + return new KeyManager[] { + new QpidClientX509KeyManager( certAlias, keyStorePath, keyStoreType, keyStorePassword, + keyManagerFactoryAlgorithm) + }; + + } + else + { + final java.security.KeyStore ks = SSLUtil.getInitializedKeyStore(keyStorePath, keyStorePassword, keyStoreType); + + char[] keyStoreCharPassword = keyStorePassword == null ? null : keyStorePassword.toCharArray(); + + final KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm); + + kmf.init(ks, keyStoreCharPassword); + + return kmf.getKeyManagers(); + } + } + catch (IOException e) + { + throw new GeneralSecurityException(e); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/NoStatistics.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/NoStatistics.java new file mode 100644 index 0000000000..03fdbd1e85 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/NoStatistics.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.model.adapter; + +import org.apache.qpid.server.model.Statistics; + +import java.util.Collection; +import java.util.Collections; + +public class NoStatistics implements Statistics +{ + private static final NoStatistics INSTANCE = new NoStatistics(); + + private NoStatistics() + { + } + + public Collection getStatisticNames() + { + return Collections.emptyList(); + } + + public Object getStatistic(String name) + { + return null; + } + + public static NoStatistics getInstance() + { + return INSTANCE; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java new file mode 100644 index 0000000000..0547f961d0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortAdapter.java @@ -0,0 +1,559 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.model.adapter; + +import java.lang.reflect.Type; +import java.security.AccessControlException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Connection; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Protocol; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.VirtualHostAlias; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.util.MapValueConverter; +import org.apache.qpid.server.util.ParameterizedTypeImpl; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.updater.TaskExecutor; + +public class PortAdapter extends AbstractAdapter implements Port +{ + @SuppressWarnings("serial") + public static final Map ATTRIBUTE_TYPES = Collections.unmodifiableMap(new HashMap(){{ + put(NAME, String.class); + put(PROTOCOLS, new ParameterizedTypeImpl(Set.class, Protocol.class)); + put(TRANSPORTS, new ParameterizedTypeImpl(Set.class, Transport.class)); + put(TRUST_STORES, new ParameterizedTypeImpl(Set.class, String.class)); + put(KEY_STORE, String.class); + put(PORT, Integer.class); + put(TCP_NO_DELAY, Boolean.class); + put(RECEIVE_BUFFER_SIZE, Integer.class); + put(SEND_BUFFER_SIZE, Integer.class); + put(NEED_CLIENT_AUTH, Boolean.class); + put(WANT_CLIENT_AUTH, Boolean.class); + put(BINDING_ADDRESS, String.class); + put(STATE, State.class); + put(AUTHENTICATION_PROVIDER, String.class); + }}); + + private final Broker _broker; + private AuthenticationProvider _authenticationProvider; + private AtomicReference _state; + + public PortAdapter(UUID id, Broker broker, Map attributes, Map defaults, TaskExecutor taskExecutor) + { + super(id, defaults, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES), taskExecutor); + _broker = broker; + State state = MapValueConverter.getEnumAttribute(State.class, STATE, attributes, State.INITIALISING); + + Collection protocols = getProtocols(); + boolean rmiRegistry = protocols != null && protocols.contains(Protocol.RMI); + if (!rmiRegistry) + { + String authProvider = (String)getAttribute(Port.AUTHENTICATION_PROVIDER); + if (authProvider == null) + { + throw new IllegalConfigurationException("An authentication provider must be specified for port : " + getName()); + } + _authenticationProvider = broker.findAuthenticationProviderByName(authProvider); + + if(_authenticationProvider == null) + { + throw new IllegalConfigurationException("The authentication provider '" + authProvider + "' could not be found for port : " + getName()); + } + } + + _state = new AtomicReference(state); + addParent(Broker.class, broker); + } + + @Override + public String getBindingAddress() + { + return (String)getAttribute(BINDING_ADDRESS); + } + + @Override + public int getPort() + { + return (Integer)getAttribute(PORT); + } + + @SuppressWarnings("unchecked") + @Override + public Collection getTransports() + { + return (Collection)getAttribute(TRANSPORTS); + } + + @Override + public void addTransport(Transport transport) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + @Override + public Transport removeTransport(Transport transport) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + @SuppressWarnings("unchecked") + @Override + public Collection getProtocols() + { + return (Collection)getAttribute(PROTOCOLS); + } + + @Override + public void addProtocol(Protocol protocol) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + @Override + public Protocol removeProtocol(Protocol protocol) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + @Override + public Collection getVirtualHostBindings() + { + List aliases = new ArrayList(); + for(VirtualHost vh : _broker.getVirtualHosts()) + { + for(VirtualHostAlias alias : vh.getAliases()) + { + if(alias.getPort().equals(this)) + { + aliases.add(alias); + } + } + } + return Collections.unmodifiableCollection(aliases); + } + + @Override + public Collection getConnections() + { + return null; + } + + @Override + public String getName() + { + return (String)getAttribute(NAME); + } + + @Override + public String setName(String currentName, String desiredName) throws IllegalStateException, AccessControlException + { + throw new IllegalStateException(); + } + + @Override + public State getActualState() + { + return _state.get(); + } + + @Override + public boolean isDurable() + { + return false; + } + + @Override + public void setDurable(boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + @Override + public long getTimeToLive() + { + return 0; + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public Collection getChildren(Class clazz) + { + if(clazz == Connection.class) + { + return (Collection) getConnections(); + } + else + { + return Collections.emptySet(); + } + } + + @Override + public C createChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + throw new UnsupportedOperationException(); + } + + @Override + public Object getAttribute(String name) + { + if(ID.equals(name)) + { + return getId(); + } + else if(STATE.equals(name)) + { + return getActualState(); + } + else if(DURABLE.equals(name)) + { + return isDurable(); + } + else if(LIFETIME_POLICY.equals(name)) + { + return getLifetimePolicy(); + } + else if(TIME_TO_LIVE.equals(name)) + { + return getTimeToLive(); + } + else if(CREATED.equals(name)) + { + + } + else if(UPDATED.equals(name)) + { + + } + return super.getAttribute(name); + } + + @Override + public Collection getAttributeNames() + { + return AVAILABLE_ATTRIBUTES; + } + + @Override + public boolean setState(State currentState, State desiredState) + { + State state = _state.get(); + if (desiredState == State.DELETED) + { + if (state == State.INITIALISING || state == State.ACTIVE || state == State.STOPPED || state == State.QUIESCED || state == State.ERRORED) + { + return _state.compareAndSet(state, State.DELETED); + } + else + { + throw new IllegalStateException("Cannot delete port in " + state + " state"); + } + } + else if (desiredState == State.ACTIVE) + { + if ((state == State.INITIALISING || state == State.QUIESCED) && _state.compareAndSet(state, State.ACTIVE)) + { + try + { + onActivate(); + } + catch(RuntimeException e) + { + _state.compareAndSet(State.ACTIVE, State.ERRORED); + throw e; + } + return true; + } + else + { + throw new IllegalStateException("Cannot activate port in " + state + " state"); + } + } + else if (desiredState == State.QUIESCED) + { + if (state == State.INITIALISING && _state.compareAndSet(state, State.QUIESCED)) + { + return true; + } + } + else if (desiredState == State.STOPPED) + { + if (_state.compareAndSet(state, State.STOPPED)) + { + onStop(); + return true; + } + else + { + throw new IllegalStateException("Cannot stop port in " + state + " state"); + } + } + return false; + } + + protected void onActivate() + { + // no-op: expected to be overridden by subclass + } + + protected void onStop() + { + // no-op: expected to be overridden by subclass + } + + @Override + public AuthenticationProvider getAuthenticationProvider() + { + return _authenticationProvider; + } + + @Override + protected void changeAttributes(Map attributes) + { + Map converted = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + + Map merged = generateEffectiveAttributes(converted); + + String newName = (String) merged.get(NAME); + if(!getName().equals(newName)) + { + throw new IllegalConfigurationException("Changing the port name is not allowed"); + } + + Integer newPort = (Integer) merged.get(PORT); + if(getPort() != newPort) + { + for(Port p : _broker.getPorts()) + { + if(p.getPort() == newPort) + { + throw new IllegalConfigurationException("Port number " + newPort + " is already in use by port " + p.getName()); + } + } + } + + @SuppressWarnings("unchecked") + Collection transports = (Collection)merged.get(TRANSPORTS); + @SuppressWarnings("unchecked") + Collection protocols = (Collection)merged.get(PROTOCOLS); + Boolean needClientCertificate = (Boolean)merged.get(NEED_CLIENT_AUTH); + Boolean wantClientCertificate = (Boolean)merged.get(WANT_CLIENT_AUTH); + boolean requiresCertificate = (needClientCertificate != null && needClientCertificate.booleanValue()) + || (wantClientCertificate != null && wantClientCertificate.booleanValue()); + + String keyStoreName = (String) merged.get(KEY_STORE); + if(keyStoreName != null) + { + if (_broker.findKeyStoreByName(keyStoreName) == null) + { + throw new IllegalConfigurationException("Can't find key store with name '" + keyStoreName + "' for port " + getName()); + } + } + + Set trustStoreNames = (Set) merged.get(TRUST_STORES); + boolean hasTrustStore = trustStoreNames != null && !trustStoreNames.isEmpty(); + if(hasTrustStore) + { + for (String trustStoreName : trustStoreNames) + { + if (_broker.findTrustStoreByName(trustStoreName) == null) + { + throw new IllegalConfigurationException("Cannot find trust store with name '" + trustStoreName + "'"); + } + } + } + + boolean usesSsl = transports != null && transports.contains(Transport.SSL); + if (usesSsl) + { + if (keyStoreName == null) + { + throw new IllegalConfigurationException("Can't create port which requires SSL but has no key store configured."); + } + + if (!hasTrustStore && requiresCertificate) + { + throw new IllegalConfigurationException("Can't create port which requests SSL client certificates but has no trust store configured."); + } + } + else + { + if (requiresCertificate) + { + throw new IllegalConfigurationException("Can't create port which requests SSL client certificates but doesn't use SSL transport."); + } + } + + if (protocols != null && protocols.contains(Protocol.RMI) && usesSsl) + { + throw new IllegalConfigurationException("Can't create RMI Registry port which requires SSL."); + } + + String authenticationProviderName = (String)merged.get(AUTHENTICATION_PROVIDER); + if (authenticationProviderName != null) + { + Collection providers = _broker.getAuthenticationProviders(); + AuthenticationProvider provider = null; + for (AuthenticationProvider p : providers) + { + if (p.getName().equals(authenticationProviderName)) + { + provider = p; + break; + } + } + + if (provider == null) + { + throw new IllegalConfigurationException("Cannot find authentication provider with name '" + + authenticationProviderName + "'"); + } + } + else + { + if (protocols != null && !protocols.contains(Protocol.RMI)) + { + throw new IllegalConfigurationException("An authentication provider must be specified"); + } + } + + super.changeAttributes(converted); + } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), Port.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of port is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), Port.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of port attributes is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), Port.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of port attributes is denied"); + } + } + + @Override + public KeyStore getKeyStore() + { + String keyStoreName = (String)getAttribute(Port.KEY_STORE); + KeyStore keyStore = _broker.findKeyStoreByName(keyStoreName); + + if (keyStoreName != null && keyStore == null) + { + throw new IllegalConfigurationException("Can't find key store with name '" + keyStoreName + "' for port " + getName()); + } + + return keyStore; + } + + @Override + public Collection getTrustStores() + { + Set trustStoreNames = (Set) getAttribute(TRUST_STORES); + boolean hasTrustStoreName = trustStoreNames != null && !trustStoreNames.isEmpty(); + + final Collection trustStores = new ArrayList(); + if(hasTrustStoreName) + { + for (String name : trustStoreNames) + { + TrustStore trustStore = _broker.findTrustStoreByName(name); + if (trustStore == null) + { + throw new IllegalConfigurationException("Can't find trust store with name '" + name + "' for port " + getName()); + } + + trustStores.add(trustStore); + } + } + + return trustStores; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortFactory.java new file mode 100644 index 0000000000..8dc446e5b2 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PortFactory.java @@ -0,0 +1,192 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Protocol; +import org.apache.qpid.server.model.Protocol.ProtocolType; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.util.MapValueConverter; + +public class PortFactory +{ + public static final int DEFAULT_AMQP_SEND_BUFFER_SIZE = 262144; + public static final int DEFAULT_AMQP_RECEIVE_BUFFER_SIZE = 262144; + public static final boolean DEFAULT_AMQP_NEED_CLIENT_AUTH = false; + public static final boolean DEFAULT_AMQP_WANT_CLIENT_AUTH = false; + public static final boolean DEFAULT_AMQP_TCP_NO_DELAY = true; + public static final String DEFAULT_AMQP_BINDING = "*"; + public static final Transport DEFAULT_TRANSPORT = Transport.TCP; + + private final Collection _defaultProtocols; + + public PortFactory() + { + Set defaultProtocols = EnumSet.of(Protocol.AMQP_0_8, Protocol.AMQP_0_9, Protocol.AMQP_0_9_1, + Protocol.AMQP_0_10, Protocol.AMQP_1_0); + String excludedProtocols = System.getProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_EXCLUDES); + if (excludedProtocols != null) + { + String[] excludes = excludedProtocols.split(","); + for (String exclude : excludes) + { + Protocol protocol = Protocol.valueOf(exclude); + defaultProtocols.remove(protocol); + } + } + String includedProtocols = System.getProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_INCLUDES); + if (includedProtocols != null) + { + String[] includes = includedProtocols.split(","); + for (String include : includes) + { + Protocol protocol = Protocol.valueOf(include); + defaultProtocols.add(protocol); + } + } + _defaultProtocols = Collections.unmodifiableCollection(defaultProtocols); + } + + public Port createPort(UUID id, Broker broker, Map attributes) + { + final Port port; + Map defaults = new HashMap(); + defaults.put(Port.TRANSPORTS, Collections.singleton(DEFAULT_TRANSPORT)); + Object portValue = attributes.get(Port.PORT); + if (portValue == null) + { + throw new IllegalConfigurationException("Port attribute is not specified for port: " + attributes); + } + Set protocols = MapValueConverter.getEnumSetAttribute(Port.PROTOCOLS, attributes, Protocol.class); + if (isAmqpProtocol(protocols, attributes)) + { + Object binding = attributes.get(Port.BINDING_ADDRESS); + if (binding == null) + { + binding = DEFAULT_AMQP_BINDING; + defaults.put(Port.BINDING_ADDRESS, DEFAULT_AMQP_BINDING); + } + defaults.put(Port.NAME, binding + ":" + portValue); + defaults.put(Port.PROTOCOLS, _defaultProtocols); + defaults.put(Port.TCP_NO_DELAY, DEFAULT_AMQP_TCP_NO_DELAY); + defaults.put(Port.WANT_CLIENT_AUTH, DEFAULT_AMQP_WANT_CLIENT_AUTH); + defaults.put(Port.NEED_CLIENT_AUTH, DEFAULT_AMQP_NEED_CLIENT_AUTH); + defaults.put(Port.RECEIVE_BUFFER_SIZE, DEFAULT_AMQP_RECEIVE_BUFFER_SIZE); + defaults.put(Port.SEND_BUFFER_SIZE, DEFAULT_AMQP_SEND_BUFFER_SIZE); + port = new AmqpPortAdapter(id, broker, attributes, defaults, broker.getTaskExecutor()); + + boolean useClientAuth = (Boolean) port.getAttribute(Port.NEED_CLIENT_AUTH) || (Boolean) port.getAttribute(Port.WANT_CLIENT_AUTH); + if(useClientAuth && port.getTrustStores().isEmpty()) + { + throw new IllegalConfigurationException("Can't create port which requests SSL client certificates but has no trust stores configured."); + } + + if(useClientAuth && !port.getTransports().contains(Transport.SSL)) + { + throw new IllegalConfigurationException("Can't create port which requests SSL client certificates but doesn't use SSL transport."); + } + } + else + { + if (protocols.size() > 1) + { + throw new IllegalConfigurationException("Only one protocol can be used on non AMQP port"); + } + Protocol protocol = protocols.iterator().next(); + + if(!broker.isManagementMode() && protocol.getProtocolType() != ProtocolType.HTTP) + { + //ManagementMode needs this relaxed to allow its overriding management ports to be inserted. + + //Enforce only a single port of each management protocol, as the plugins will only use one. + Collection existingPorts = broker.getPorts(); + for (Port existingPort : existingPorts) + { + Collection portProtocols = existingPort.getProtocols(); + if (portProtocols != null && portProtocols.contains(protocol)) + { + throw new IllegalConfigurationException("Port for protocol " + protocol + " already exists. Only one management port per protocol can be created."); + } + } + } + + defaults.put(Port.NAME, portValue + "-" + protocol.name()); + port = new PortAdapter(id, broker, attributes, defaults, broker.getTaskExecutor()); + + boolean rmiPort = port.getProtocols().contains(Protocol.RMI); + if (rmiPort && port.getTransports().contains(Transport.SSL)) + { + throw new IllegalConfigurationException("Can't create RMI registry port which requires SSL"); + } + } + + if(port.getTransports().contains(Transport.SSL)) + { + if(port.getKeyStore() == null) + { + throw new IllegalConfigurationException("Can't create port which requires SSL but has no key store configured."); + } + } + + return port; + } + + private boolean isAmqpProtocol(Set protocols, Map portAttributes) + { + if (protocols == null || protocols.isEmpty()) + { + // defaulting to AMQP if protocol is not specified + return true; + } + + Set protocolTypes = new HashSet(); + for (Protocol protocolObject : protocols) + { + protocolTypes.add(protocolObject.getProtocolType()); + } + + if (protocolTypes.size() > 1) + { + throw new IllegalConfigurationException("Found different protocol types '" + protocolTypes + + "' for port configuration: " + portAttributes); + } + + return protocolTypes.contains(ProtocolType.AMQP); + } + + public Collection getDefaultProtocols() + { + return _defaultProtocols; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PreferencesProviderCreator.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PreferencesProviderCreator.java new file mode 100644 index 0000000000..4bcca0e300 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/PreferencesProviderCreator.java @@ -0,0 +1,66 @@ +package org.apache.qpid.server.model.adapter; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.plugin.PreferencesProviderFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; + +public class PreferencesProviderCreator +{ + private final Map _factories; + private Collection _supportedPreferencesProviders; + + public PreferencesProviderCreator() + { + QpidServiceLoader preferencesProviderFactoriess = new QpidServiceLoader(); + + Iterable factories = preferencesProviderFactoriess + .instancesOf(PreferencesProviderFactory.class); + + Map registeredPreferencesProviderFactories = new HashMap(); + for (PreferencesProviderFactory factory : factories) + { + PreferencesProviderFactory existingFactory = registeredPreferencesProviderFactories.put(factory.getType(), + factory); + if (existingFactory != null) + { + throw new IllegalConfigurationException("Preferences provider factory of the same type '" + + factory.getType() + "' is already registered using class '" + existingFactory.getClass().getName() + + "', can not register class '" + factory.getClass().getName() + "'"); + } + } + _factories = registeredPreferencesProviderFactories; + _supportedPreferencesProviders = Collections.unmodifiableCollection(registeredPreferencesProviderFactories.keySet()); + } + + public Collection getSupportedPreferencesProviders() + { + return _supportedPreferencesProviders; + } + + public PreferencesProvider create(UUID id, Map attributes, AuthenticationProvider authenticationProvider) + { + return createPreferencesProvider(id, attributes, authenticationProvider); + } + + public PreferencesProvider recover(UUID id, Map attributes, AuthenticationProvider authenticationProviderr) + { + return createPreferencesProvider(id, attributes, authenticationProviderr); + } + + private PreferencesProvider createPreferencesProvider(UUID id, Map attributes, AuthenticationProvider authenticationProvider) + { + for (PreferencesProviderFactory factory : _factories.values()) + { + return factory.createInstance(id, attributes, authenticationProvider); + } + throw new IllegalConfigurationException("No group provider factory found for configuration attributes " + attributes); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/QueueAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/QueueAdapter.java new file mode 100644 index 0000000000..074f7c243b --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/QueueAdapter.java @@ -0,0 +1,816 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.lang.reflect.Type; +import java.security.AccessControlException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.ConfiguredObjectFinder; +import org.apache.qpid.server.model.Consumer; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.IllegalStateTransitionException; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.QueueNotificationListener; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.*; +import org.apache.qpid.server.store.DurableConfigurationStoreHelper; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.util.MapValueConverter; + +final class QueueAdapter extends AbstractAdapter implements Queue, AMQQueue.SubscriptionRegistrationListener, AMQQueue.NotificationListener +{ + @SuppressWarnings("serial") + static final Map ATTRIBUTE_TYPES = Collections.unmodifiableMap(new HashMap(){{ + put(ALERT_REPEAT_GAP, Long.class); + put(ALERT_THRESHOLD_MESSAGE_AGE, Long.class); + put(ALERT_THRESHOLD_MESSAGE_SIZE, Long.class); + put(ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, Long.class); + put(ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, Long.class); + put(QUEUE_FLOW_CONTROL_SIZE_BYTES, Long.class); + put(QUEUE_FLOW_RESUME_SIZE_BYTES, Long.class); + put(MAXIMUM_DELIVERY_ATTEMPTS, Integer.class); + put(EXCLUSIVE, Boolean.class); + put(DESCRIPTION, String.class); + }}); + + private final AMQQueue _queue; + private final Map _bindingAdapters = + new HashMap(); + private Map _consumerAdapters = + new HashMap(); + + + private final VirtualHostAdapter _vhost; + private QueueStatisticsAdapter _statistics; + private QueueNotificationListener _queueNotificationListener; + + public QueueAdapter(final VirtualHostAdapter virtualHostAdapter, final AMQQueue queue) + { + super(queue.getId(), virtualHostAdapter.getTaskExecutor()); + _vhost = virtualHostAdapter; + addParent(org.apache.qpid.server.model.VirtualHost.class, virtualHostAdapter); + + _queue = queue; + _queue.addSubscriptionRegistrationListener(this); + populateConsumers(); + _statistics = new QueueStatisticsAdapter(queue); + _queue.setNotificationListener(this); + } + + /** + * Helper method to retrieve the SessionAdapter keyed by the AMQSessionModel. + * This method first finds the ConnectionAdapter associated with the Session from this QueueAdapter's parent vhost + * then it does a lookup on that to find the SessionAdapter keyed by the requested AMQSessionModel instance. + * @param session the AMQSessionModel used to index the SessionAdapter. + * @return the requested SessionAdapter or null if it can't be found. + */ + private SessionAdapter getSessionAdapter(AMQSessionModel session) + { + // Retrieve the ConnectionModel associated with the SessionModel as a key to lookup the ConnectionAdapter. + AMQConnectionModel connectionKey = session.getConnectionModel(); + + // Lookup the ConnectionAdapter, from which we should be able to retrieve the SessionAdapter we really want. + ConnectionAdapter connectionAdapter = _vhost.getConnectionAdapter(connectionKey); + if (connectionAdapter == null) + { + return null; // If we can't find an associated ConnectionAdapter the SessionAdapter is a lost cause. + } + else + { // With a good ConnectionAdapter we can finally try to find the SessionAdapter we are actually looking for. + SessionAdapter sessionAdapter = connectionAdapter.getSessionAdapter(session); + if (sessionAdapter == null) + { + return null; // If the SessionAdapter isn't associated with the selected ConnectionAdapter give up. + } + else + { + return sessionAdapter; + } + } + } + + private void populateConsumers() + { + Collection actualSubscriptions = _queue.getConsumers(); + + synchronized (_consumerAdapters) + { + Iterator iter = _consumerAdapters.keySet().iterator(); + for(org.apache.qpid.server.subscription.Subscription subscription : actualSubscriptions) + { + if(!_consumerAdapters.containsKey(subscription)) + { + SessionAdapter sessionAdapter = getSessionAdapter(subscription.getSessionModel()); + ConsumerAdapter adapter = new ConsumerAdapter(this, sessionAdapter, subscription); + _consumerAdapters.put(subscription, adapter); + if (sessionAdapter != null) + { // Register ConsumerAdapter with the SessionAdapter. + sessionAdapter.subscriptionRegistered(subscription, adapter); + } + } + } + } + } + + public Collection getBindings() + { + synchronized (_bindingAdapters) + { + return new ArrayList(_bindingAdapters.values()); + } + } + + public Collection getConsumers() + { + synchronized (_consumerAdapters) + { + return new ArrayList(_consumerAdapters.values()); + } + + } + + public void visit(final QueueEntryVisitor visitor) + { + _queue.visit(visitor); + } + + public void delete() + { + try + { + _queue.getVirtualHost().removeQueue(_queue); + } + catch(AMQException e) + { + throw new IllegalStateException(e); + } + } + + public String getName() + { + return _queue.getName(); + } + + public String setName(final String currentName, final String desiredName) + throws IllegalStateException, AccessControlException + { + return null; //TODO + } + + public State getActualState() + { + return null; //TODO + } + + public boolean isDurable() + { + return _queue.isDurable(); + } + + public void setDurable(final boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + //TODO + } + + public LifetimePolicy getLifetimePolicy() + { + return _queue.isAutoDelete() ? LifetimePolicy.AUTO_DELETE : LifetimePolicy.PERMANENT; + } + + public LifetimePolicy setLifetimePolicy(final LifetimePolicy expected, final LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return null; //TODO + } + + public long getTimeToLive() + { + return 0; //TODO + } + + public long setTimeToLive(final long expected, final long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return 0; //TODO + } + + + @Override + public Collection getAttributeNames() + { + return Queue.AVAILABLE_ATTRIBUTES; + } + + @Override + public boolean changeAttribute(String name, Object expected, Object desired) throws IllegalStateException, AccessControlException, IllegalArgumentException + { + try + { + if(ALERT_REPEAT_GAP.equals(name)) + { + _queue.setMinimumAlertRepeatGap((Long)desired); + return true; + } + else if(ALERT_THRESHOLD_MESSAGE_AGE.equals(name)) + { + _queue.setMaximumMessageAge((Long)desired); + return true; + } + else if(ALERT_THRESHOLD_MESSAGE_SIZE.equals(name)) + { + _queue.setMaximumMessageSize((Long)desired); + return true; + } + else if(ALERT_THRESHOLD_QUEUE_DEPTH_BYTES.equals(name)) + { + _queue.setMaximumQueueDepth((Long)desired); + return true; + } + else if(ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES.equals(name)) + { + _queue.setMaximumMessageCount((Long)desired); + return true; + } + else if(ALTERNATE_EXCHANGE.equals(name)) + { + // In future we may want to accept a UUID as an alternative way to identifying the exchange + ExchangeAdapter alternateExchange = (ExchangeAdapter) desired; + _queue.setAlternateExchange(alternateExchange == null ? null : alternateExchange.getExchange()); + return true; + } + else if(EXCLUSIVE.equals(name)) + { + Boolean exclusiveFlag = (Boolean) desired; + _queue.setExclusive(exclusiveFlag); + return true; + } + else if(MESSAGE_GROUP_KEY.equals(name)) + { + // TODO + } + else if(MESSAGE_GROUP_SHARED_GROUPS.equals(name)) + { + // TODO + } + else if(LVQ_KEY.equals(name)) + { + // TODO + } + else if(MAXIMUM_DELIVERY_ATTEMPTS.equals(name)) + { + _queue.setMaximumDeliveryCount((Integer)desired); + return true; + } + else if(NO_LOCAL.equals(name)) + { + // TODO + } + else if(OWNER.equals(name)) + { + // TODO + } + else if(QUEUE_FLOW_CONTROL_SIZE_BYTES.equals(name)) + { + _queue.setCapacity((Long)desired); + return true; + } + else if(QUEUE_FLOW_RESUME_SIZE_BYTES.equals(name)) + { + _queue.setFlowResumeCapacity((Long)desired); + return true; + } + else if(QUEUE_FLOW_STOPPED.equals(name)) + { + // TODO + } + else if(SORT_KEY.equals(name)) + { + // TODO + } + else if(TYPE.equals(name)) + { + // TODO + } + else if (DESCRIPTION.equals(name)) + { + _queue.setDescription((String) desired); + return true; + } + + return super.changeAttribute(name, expected, desired); + } + finally + { + if (_queue.isDurable()) + { + try + { + DurableConfigurationStoreHelper.updateQueue(_queue.getVirtualHost().getDurableConfigurationStore(), + _queue); + } + catch (AMQStoreException e) + { + throw new IllegalStateException(e); + } + } + } + } + + @Override + public Object getAttribute(String name) + { + + if(ALERT_REPEAT_GAP.equals(name)) + { + return _queue.getMinimumAlertRepeatGap(); + } + else if(ALERT_THRESHOLD_MESSAGE_AGE.equals(name)) + { + return _queue.getMaximumMessageAge(); + } + else if(ALERT_THRESHOLD_MESSAGE_SIZE.equals(name)) + { + return _queue.getMaximumMessageSize(); + } + else if(ALERT_THRESHOLD_QUEUE_DEPTH_BYTES.equals(name)) + { + return _queue.getMaximumQueueDepth(); + } + else if(ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES.equals(name)) + { + return _queue.getMaximumMessageCount(); + } + else if(ALTERNATE_EXCHANGE.equals(name)) + { + org.apache.qpid.server.exchange.Exchange alternateExchange = _queue.getAlternateExchange(); + return alternateExchange == null ? null : + ConfiguredObjectFinder.findConfiguredObjectByName(_vhost.getExchanges(), + alternateExchange.getName()); + } + else if(EXCLUSIVE.equals(name)) + { + return _queue.isExclusive(); + } + else if(MESSAGE_GROUP_KEY.equals(name)) + { + return _queue.getAttribute(MESSAGE_GROUP_KEY); + } + else if(MESSAGE_GROUP_SHARED_GROUPS.equals(name)) + { + //We only return the boolean value if message groups are actually in use + return getAttribute(MESSAGE_GROUP_KEY) == null ? null : _queue.getAttribute(MESSAGE_GROUP_SHARED_GROUPS); + } + else if(LVQ_KEY.equals(name)) + { + if(_queue instanceof ConflationQueue) + { + return ((ConflationQueue)_queue).getConflationKey(); + } + } + else if(MAXIMUM_DELIVERY_ATTEMPTS.equals(name)) + { + return _queue.getMaximumDeliveryCount(); + } + else if(NO_LOCAL.equals(name)) + { + // TODO + } + else if(OWNER.equals(name)) + { + return _queue.getOwner(); + } + else if(QUEUE_FLOW_CONTROL_SIZE_BYTES.equals(name)) + { + return _queue.getCapacity(); + } + else if(QUEUE_FLOW_RESUME_SIZE_BYTES.equals(name)) + { + return _queue.getFlowResumeCapacity(); + } + else if(QUEUE_FLOW_STOPPED.equals(name)) + { + return _queue.isOverfull(); + } + else if(SORT_KEY.equals(name)) + { + if(_queue instanceof SortedQueue) + { + return ((SortedQueue)_queue).getSortedPropertyName(); + } + } + else if(TYPE.equals(name)) + { + if(_queue instanceof SortedQueue) + { + return "sorted"; + } + if(_queue instanceof ConflationQueue) + { + return "lvq"; + } + if(_queue instanceof AMQPriorityQueue) + { + return "priority"; + } + return "standard"; + } + else if(CREATED.equals(name)) + { + // TODO + } + else if(DURABLE.equals(name)) + { + return _queue.isDurable(); + } + else if(ID.equals(name)) + { + return getId(); + } + else if(LIFETIME_POLICY.equals(name)) + { + return _queue.isAutoDelete() ? LifetimePolicy.AUTO_DELETE : LifetimePolicy.PERMANENT; + } + else if(NAME.equals(name)) + { + return _queue.getName(); + } + else if(STATE.equals(name)) + { + return State.ACTIVE; // TODO + } + else if(TIME_TO_LIVE.equals(name)) + { + // TODO + } + else if(UPDATED.equals(name)) + { + // TODO + } + else if (DESCRIPTION.equals(name)) + { + return _queue.getDescription(); + } + else if(PRIORITIES.equals(name)) + { + if(_queue instanceof AMQPriorityQueue) + { + return ((AMQPriorityQueue)_queue).getPriorities(); + } + } + return super.getAttribute(name); + } + + public Statistics getStatistics() + { + return _statistics; + } + + @Override + public Collection getChildren(Class clazz) + { + if(clazz == Consumer.class) + { + return (Collection) getConsumers(); + } + else if(clazz == org.apache.qpid.server.model.Binding.class) + { + return (Collection) getBindings(); + } + else + { + return Collections.emptySet(); + } + } + + public org.apache.qpid.server.model.Binding createBinding(Exchange exchange, Map attributes) + throws AccessControlException, IllegalStateException + { + attributes = new HashMap(attributes); + String bindingKey = MapValueConverter.getStringAttribute(org.apache.qpid.server.model.Binding.NAME, attributes, ""); + Map bindingArgs = MapValueConverter.getMapAttribute(org.apache.qpid.server.model.Binding.ARGUMENTS, attributes, Collections.emptyMap()); + + attributes.remove(org.apache.qpid.server.model.Binding.NAME); + attributes.remove(org.apache.qpid.server.model.Binding.ARGUMENTS); + + return exchange.createBinding(bindingKey, this, bindingArgs, attributes); + + } + + + + @Override + public C addChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + if(childClass == org.apache.qpid.server.model.Binding.class) + { + if(otherParents != null && otherParents.length == 1 && otherParents[0] instanceof Exchange) + { + Exchange exchange = (Exchange) otherParents[0]; + if(exchange.getParent(org.apache.qpid.server.model.VirtualHost.class) == getParent(org.apache.qpid.server.model.VirtualHost.class)) + { + return (C) createBinding(exchange, attributes); + } + else + { + throw new IllegalArgumentException("Queue and Exchange parents of a binding must be on same virtual host"); + } + } + else + { + throw new IllegalArgumentException("Other parent must be an exchange"); + } + } + else + { + throw new IllegalArgumentException(); + } + } + + void bindingRegistered(Binding binding, BindingAdapter adapter) + { + synchronized (_bindingAdapters) + { + _bindingAdapters.put(binding, adapter); + } + childAdded(adapter); + } + + void bindingUnregistered(Binding binding) + { + BindingAdapter adapter = null; + synchronized (_bindingAdapters) + { + adapter = _bindingAdapters.remove(binding); + } + if(adapter != null) + { + childRemoved(adapter); + } + } + + AMQQueue getAMQQueue() + { + return _queue; + } + + public void subscriptionRegistered(final AMQQueue queue, final Subscription subscription) + { + ConsumerAdapter adapter = null; + synchronized (_consumerAdapters) + { + if(!_consumerAdapters.containsKey(subscription)) + { + SessionAdapter sessionAdapter = getSessionAdapter(subscription.getSessionModel()); + adapter = new ConsumerAdapter(this, sessionAdapter, subscription); + _consumerAdapters.put(subscription, adapter); + if (sessionAdapter != null) + { // Register ConsumerAdapter with the SessionAdapter. + sessionAdapter.subscriptionRegistered(subscription, adapter); + } + } + } + if(adapter != null) + { + childAdded(adapter); + } + } + + public void subscriptionUnregistered(final AMQQueue queue, final Subscription subscription) + { + ConsumerAdapter adapter = null; + + synchronized (_consumerAdapters) + { + adapter = _consumerAdapters.remove(subscription); + } + if(adapter != null) + { + SessionAdapter sessionAdapter = getSessionAdapter(subscription.getSessionModel()); + if (sessionAdapter != null) + { // Unregister ConsumerAdapter with the SessionAdapter. + sessionAdapter.subscriptionUnregistered(subscription); + } + childRemoved(adapter); + } + } + + VirtualHostAdapter getVirtualHost() + { + return _vhost; + } + + + private static class QueueStatisticsAdapter implements Statistics + { + + private final AMQQueue _queue; + + public QueueStatisticsAdapter(AMQQueue queue) + { + _queue = queue; + } + + public Collection getStatisticNames() + { + return Queue.AVAILABLE_STATISTICS; + } + + public Object getStatistic(String name) + { + if(BINDING_COUNT.equals(name)) + { + return _queue.getBindingCount(); + } + else if(CONSUMER_COUNT.equals(name)) + { + return _queue.getConsumerCount(); + } + else if(CONSUMER_COUNT_WITH_CREDIT.equals(name)) + { + return _queue.getActiveConsumerCount(); + } + else if(DISCARDS_TTL_BYTES.equals(name)) + { + return null; // TODO + } + else if(DISCARDS_TTL_MESSAGES.equals(name)) + { + return null; // TODO + } + else if(PERSISTENT_DEQUEUED_BYTES.equals(name)) + { + return _queue.getPersistentByteDequeues(); + } + else if(PERSISTENT_DEQUEUED_MESSAGES.equals(name)) + { + return _queue.getPersistentMsgDequeues(); + } + else if(PERSISTENT_ENQUEUED_BYTES.equals(name)) + { + return _queue.getPersistentByteEnqueues(); + } + else if(PERSISTENT_ENQUEUED_MESSAGES.equals(name)) + { + return _queue.getPersistentMsgEnqueues(); + } + else if(QUEUE_DEPTH_BYTES.equals(name)) + { + return _queue.getQueueDepth(); + } + else if(QUEUE_DEPTH_MESSAGES.equals(name)) + { + return _queue.getMessageCount(); + } + else if(STATE_CHANGED.equals(name)) + { + return null; // TODO + } + else if(TOTAL_DEQUEUED_BYTES.equals(name)) + { + return _queue.getTotalDequeueSize(); + } + else if(TOTAL_DEQUEUED_MESSAGES.equals(name)) + { + return _queue.getTotalDequeueCount(); + } + else if(TOTAL_ENQUEUED_BYTES.equals(name)) + { + return _queue.getTotalEnqueueSize(); + } + else if(TOTAL_ENQUEUED_MESSAGES.equals(name)) + { + return _queue.getTotalEnqueueCount(); + } + else if(UNACKNOWLEDGED_BYTES.equals(name)) + { + return _queue.getUnackedMessageBytes(); + } + else if(UNACKNOWLEDGED_MESSAGES.equals(name)) + { + return _queue.getUnackedMessageCount(); + } + + return null; + } + } + + @Override + public void setNotificationListener(QueueNotificationListener listener) + { + _queueNotificationListener = listener; + } + + @Override + public void notifyClients(NotificationCheck notification, AMQQueue queue, String notificationMsg) + { + QueueNotificationListener listener = _queueNotificationListener; + if(listener != null) + { + listener.notifyClients(notification, this, notificationMsg); + } + } + + @Override + protected boolean setState(State currentState, State desiredState) throws IllegalStateTransitionException, + AccessControlException + { + if (desiredState == State.DELETED) + { + delete(); + return true; + } + return false; + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_vhost.getSecurityManager().authoriseUpdate(_queue)) + { + throw new AccessControlException("Setting of queue attribute is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + if (!_vhost.getSecurityManager().authoriseUpdate(_queue)) + { + throw new AccessControlException("Setting of queue attributes is denied"); + } + } + + @Override + protected void changeAttributes(final Map attributes) + { + Map convertedAttributes = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + validateAttributes(convertedAttributes); + + super.changeAttributes(convertedAttributes); + } + + private void validateAttributes(Map convertedAttributes) + { + Long queueFlowControlSize = (Long) convertedAttributes.get(QUEUE_FLOW_CONTROL_SIZE_BYTES); + Long queueFlowControlResumeSize = (Long) convertedAttributes.get(QUEUE_FLOW_RESUME_SIZE_BYTES); + if (queueFlowControlSize != null || queueFlowControlResumeSize != null ) + { + if (queueFlowControlSize == null) + { + queueFlowControlSize = (Long)getAttribute(QUEUE_FLOW_CONTROL_SIZE_BYTES); + } + if (queueFlowControlResumeSize == null) + { + queueFlowControlResumeSize = (Long)getAttribute(QUEUE_FLOW_RESUME_SIZE_BYTES); + } + if (queueFlowControlResumeSize > queueFlowControlSize) + { + throw new IllegalConfigurationException("Flow resume size can't be greater than flow control size"); + } + } + for (Map.Entry entry: convertedAttributes.entrySet()) + { + Object value = entry.getValue(); + if (value instanceof Number && ((Number)value).longValue() < 0) + { + throw new IllegalConfigurationException("Only positive integer value can be specified for the attribute " + + entry.getKey()); + } + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/SessionAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/SessionAdapter.java new file mode 100644 index 0000000000..31ce7e56fd --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/SessionAdapter.java @@ -0,0 +1,293 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.security.AccessControlException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; + +import java.util.Map; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Publisher; +import org.apache.qpid.server.model.Session; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.Consumer; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.protocol.AMQSessionModel; + +final class SessionAdapter extends AbstractAdapter implements Session +{ + // Attributes + + + private AMQSessionModel _session; + private SessionStatistics _statistics; + private Map _consumerAdapters = new HashMap(); + + public SessionAdapter(final AMQSessionModel session, TaskExecutor taskExecutor) + { + super(UUIDGenerator.generateRandomUUID(), taskExecutor); + _session = session; + _statistics = new SessionStatistics(); + } + + public Collection getSubscriptions() + { + synchronized (_consumerAdapters) + { + return new ArrayList(_consumerAdapters.values()); + } + } + + public Collection getPublishers() + { + return null; //TODO + } + + public String getName() + { + return String.valueOf(_session.getChannelId()); + } + + public String setName(final String currentName, final String desiredName) + throws IllegalStateException, AccessControlException + { + return null; //TODO + } + + public State getActualState() + { + return null; //TODO + } + + public boolean isDurable() + { + return false; //TODO + } + + public void setDurable(final boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + //TODO + } + + public LifetimePolicy getLifetimePolicy() + { + return null; //TODO + } + + public LifetimePolicy setLifetimePolicy(final LifetimePolicy expected, final LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return null; //TODO + } + + public long getTimeToLive() + { + return 0; //TODO + } + + public long setTimeToLive(final long expected, final long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + return 0; //TODO + } + + /** + * Register a ConsumerAdapter (Subscription) with this Session keyed by the Subscription. + * @param subscription the org.apache.qpid.server.subscription.Subscription used to key the ConsumerAdapter. + * @param adapter the registered ConsumerAdapter. + */ + void subscriptionRegistered(Subscription subscription, ConsumerAdapter adapter) + { + synchronized (_consumerAdapters) + { + _consumerAdapters.put(subscription, adapter); + } + childAdded(adapter); + } + + /** + * Unregister a ConsumerAdapter (Subscription) with this Session keyed by the Subscription. + * @param subscription the org.apache.qpid.server.subscription.Subscription used to key the ConsumerAdapter. + */ + void subscriptionUnregistered(Subscription subscription) + { + ConsumerAdapter adapter = null; + synchronized (_consumerAdapters) + { + adapter = _consumerAdapters.remove(subscription); + } + if (adapter != null) + { + childRemoved(adapter); + } + } + + @Override + public Collection getAttributeNames() + { + Collection names = new HashSet(super.getAttributeNames()); + names.addAll(AVAILABLE_ATTRIBUTES); + + return Collections.unmodifiableCollection(names); + } + + @Override + public Object getAttribute(String name) + { + if(name.equals(ID)) + { + return getId(); + } + else if (name.equals(NAME)) + { + return getName(); + } + else if(name.equals(CHANNEL_ID)) + { + return _session.getChannelId(); + } + else if(name.equals(PRODUCER_FLOW_BLOCKED)) + { + return _session.getBlocking(); + } + return super.getAttribute(name); //TODO - Implement + } + + public Statistics getStatistics() + { + return _statistics; + } + + @Override + public Collection getChildren(Class clazz) + { + if(clazz == Consumer.class) + { + return (Collection) getSubscriptions(); + } + else if(clazz == Publisher.class) + { + return (Collection) getPublishers(); + } + else + { + return Collections.emptySet(); + } + } + + @Override + public C createChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + throw new UnsupportedOperationException(); + } + + private class SessionStatistics implements Statistics + { + + public SessionStatistics() + { + } + + public Collection getStatisticNames() + { + return AVAILABLE_STATISTICS; + } + + public Object getStatistic(String name) + { + if(name.equals(BYTES_IN)) + { + } + else if(name.equals(BYTES_OUT)) + { + } + else if(name.equals(CONSUMER_COUNT)) + { + return _session.getConsumerCount(); + } + else if(name.equals(LOCAL_TRANSACTION_BEGINS)) + { + return _session.getTxnStart(); + } + else if(name.equals(LOCAL_TRANSACTION_OPEN)) + { + long open = _session.getTxnStart() - (_session.getTxnCommits() + _session.getTxnRejects()); + return (Boolean) (open > 0l); + } + else if(name.equals(LOCAL_TRANSACTION_ROLLBACKS)) + { + return _session.getTxnRejects(); + } + else if(name.equals(STATE_CHANGED)) + { + } + else if(name.equals(UNACKNOWLEDGED_BYTES)) + { + } + else if(name.equals(UNACKNOWLEDGED_MESSAGES)) + { + return _session.getUnacknowledgedMessageCount(); + } + else if(name.equals(XA_TRANSACTION_BRANCH_ENDS)) + { + } + else if(name.equals(XA_TRANSACTION_BRANCH_STARTS)) + { + } + else if(name.equals(XA_TRANSACTION_BRANCH_SUSPENDS)) + { + + } + + return null; // TODO - Implement + } + } + + @Override + protected boolean setState(State currentState, State desiredState) + { + // TODO : add state management + return false; + } + + @Override + public Object setAttribute(final String name, final Object expected, final Object desired) throws IllegalStateException, + AccessControlException, IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on session is not supported."); + } + + @Override + public void setAttributes(final Map attributes) throws IllegalStateException, AccessControlException, + IllegalArgumentException + { + throw new UnsupportedOperationException("Changing attributes on session is not supported."); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/StatisticsAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/StatisticsAdapter.java new file mode 100644 index 0000000000..28c46a0339 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/StatisticsAdapter.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.model.adapter; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.stats.StatisticsCounter; +import org.apache.qpid.server.stats.StatisticsGatherer; + +class StatisticsAdapter implements Statistics +{ + + private final Map _statistics = + new HashMap(); + + + private static final String BYTES_IN = "bytesIn"; + private static final String BYTES_OUT = "bytesOut"; + private static final String MESSAGES_IN = "messagesIn"; + private static final String MESSAGES_OUT = "messagesOut"; + + private static final Collection STATISTIC_NAMES = + Collections.unmodifiableCollection(Arrays.asList(BYTES_IN, BYTES_OUT, MESSAGES_IN, MESSAGES_OUT)); + + + + public StatisticsAdapter(StatisticsGatherer statGatherer) + { + _statistics.put(BYTES_OUT, statGatherer.getDataDeliveryStatistics()); + _statistics.put(BYTES_IN, statGatherer.getDataReceiptStatistics()); + _statistics.put(MESSAGES_OUT, statGatherer.getMessageDeliveryStatistics()); + _statistics.put(MESSAGES_IN, statGatherer.getMessageReceiptStatistics()); + } + + + public Collection getStatisticNames() + { + return STATISTIC_NAMES; + } + + public Object getStatistic(String name) + { + StatisticsCounter counter = _statistics.get(name); + return counter == null ? null : counter.getTotal(); + + } + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/TrustStoreAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/TrustStoreAdapter.java new file mode 100644 index 0000000000..5e7bfff4de --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/TrustStoreAdapter.java @@ -0,0 +1,255 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.security.AccessControlException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import javax.net.ssl.X509TrustManager; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.IntegrityViolationException; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.util.MapValueConverter; +import org.apache.qpid.transport.network.security.ssl.QpidMultipleTrustManager; +import org.apache.qpid.transport.network.security.ssl.QpidPeersOnlyTrustManager; +import org.apache.qpid.transport.network.security.ssl.SSLUtil; + +public class TrustStoreAdapter extends AbstractKeyStoreAdapter implements TrustStore +{ + @SuppressWarnings("serial") + public static final Map ATTRIBUTE_TYPES = Collections.unmodifiableMap(new HashMap(){{ + put(NAME, String.class); + put(PATH, String.class); + put(PASSWORD, String.class); + put(TYPE, String.class); + put(PEERS_ONLY, Boolean.class); + put(TRUST_MANAGER_FACTORY_ALGORITHM, String.class); + }}); + + @SuppressWarnings("serial") + public static final Map DEFAULTS = Collections.unmodifiableMap(new HashMap(){{ + put(TrustStore.TYPE, DEFAULT_KEYSTORE_TYPE); + put(TrustStore.PEERS_ONLY, Boolean.FALSE); + put(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM, TrustManagerFactory.getDefaultAlgorithm()); + }}); + + private Broker _broker; + + public TrustStoreAdapter(UUID id, Broker broker, Map attributes) + { + super(id, broker, DEFAULTS, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES)); + _broker = broker; + + String trustStorePath = (String) getAttribute(TrustStore.PATH); + String trustStorePassword = getPassword(); + String trustStoreType = (String) getAttribute(TrustStore.TYPE); + String trustManagerFactoryAlgorithm = (String) getAttribute(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM); + + validateTrustStoreAttributes(trustStoreType, trustStorePath, + trustStorePassword, trustManagerFactoryAlgorithm); + } + + @Override + public Collection getAttributeNames() + { + return AVAILABLE_ATTRIBUTES; + } + + @Override + protected boolean setState(State currentState, State desiredState) + { + if(desiredState == State.DELETED) + { + // verify that it is not in use + String storeName = getName(); + + Collection ports = new ArrayList(_broker.getPorts()); + for (Port port : ports) + { + Collection trustStores = port.getTrustStores(); + for(TrustStore store : trustStores) + { + if (storeName.equals(store.getAttribute(TrustStore.NAME))) + { + throw new IntegrityViolationException("Trust store '" + storeName + "' can't be deleted as it is in use by a port: " + port.getName()); + } + } + } + + return true; + } + + return false; + } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), TrustStore.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of key store is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + authoriseSetAttribute(); + } + + @Override + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + authoriseSetAttribute(); + } + + private void authoriseSetAttribute() + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), TrustStore.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting key store attributes is denied"); + } + } + + @Override + protected void changeAttributes(Map attributes) + { + Map changedValues = MapValueConverter.convert(attributes, ATTRIBUTE_TYPES); + if(changedValues.containsKey(TrustStore.NAME)) + { + String newName = (String) changedValues.get(TrustStore.NAME); + if(!getName().equals(newName)) + { + throw new IllegalConfigurationException("Changing the trust store name is not allowed"); + } + } + + Map merged = generateEffectiveAttributes(changedValues); + + String trustStorePath = (String)merged.get(TrustStore.PATH); + String trustStorePassword = (String) merged.get(TrustStore.PASSWORD); + String trustStoreType = (String)merged.get(TrustStore.TYPE); + String trustManagerFactoryAlgorithm = (String)merged.get(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM); + + validateTrustStoreAttributes(trustStoreType, trustStorePath, + trustStorePassword, trustManagerFactoryAlgorithm); + + super.changeAttributes(changedValues); + } + + private void validateTrustStoreAttributes(String type, String trustStorePath, + String password, String trustManagerFactoryAlgorithm) + { + try + { + SSLUtil.getInitializedKeyStore(trustStorePath, password, type); + } + catch (Exception e) + { + throw new IllegalConfigurationException("Cannot instantiate trust store at " + trustStorePath, e); + } + + try + { + TrustManagerFactory.getInstance(trustManagerFactoryAlgorithm); + } + catch (NoSuchAlgorithmException e) + { + throw new IllegalConfigurationException("Unknown trustManagerFactoryAlgorithm: " + trustManagerFactoryAlgorithm); + } + } + + public TrustManager[] getTrustManagers() throws GeneralSecurityException + { + String trustStorePath = (String)getAttribute(TrustStore.PATH); + String trustStorePassword = getPassword(); + String trustStoreType = (String)getAttribute(TrustStore.TYPE); + String trustManagerFactoryAlgorithm = (String)getAttribute(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM); + + try + { + KeyStore ts = SSLUtil.getInitializedKeyStore(trustStorePath, trustStorePassword, trustStoreType); + final TrustManagerFactory tmf = TrustManagerFactory + .getInstance(trustManagerFactoryAlgorithm); + tmf.init(ts); + final Collection trustManagersCol = new ArrayList(); + final QpidMultipleTrustManager mulTrustManager = new QpidMultipleTrustManager(); + TrustManager[] delegateManagers = tmf.getTrustManagers(); + for (TrustManager tm : delegateManagers) + { + if (tm instanceof X509TrustManager) + { + if (Boolean.TRUE.equals(getAttribute(PEERS_ONLY))) + { + // truststore is supposed to trust only clients which peers certificates + // are directly in the store. CA signing will not be considered. + mulTrustManager.addTrustManager(new QpidPeersOnlyTrustManager(ts, (X509TrustManager) tm)); + } + else + { + mulTrustManager.addTrustManager((X509TrustManager) tm); + } + } + else + { + trustManagersCol.add(tm); + } + } + if (! mulTrustManager.isEmpty()) + { + trustManagersCol.add(mulTrustManager); + } + + if (trustManagersCol.isEmpty()) + { + return null; + } + else + { + return trustManagersCol.toArray(new TrustManager[trustManagersCol.size()]); + } + } + catch (IOException e) + { + throw new GeneralSecurityException(e); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java new file mode 100644 index 0000000000..58b0b76735 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAdapter.java @@ -0,0 +1,1273 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import java.io.File; +import java.lang.reflect.Type; +import java.security.AccessControlException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration.SystemConfiguration; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.configuration.XmlConfigurationUtilities.MyConfiguration; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Connection; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.IntegrityViolationException; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Protocol; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.QueueType; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.VirtualHostAlias; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.QueueArgumentsConverter; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.SimpleAMQQueue; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.txn.LocalTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.util.MapValueConverter; +import org.apache.qpid.server.plugin.VirtualHostFactory; +import org.apache.qpid.server.virtualhost.ExchangeExistsException; +import org.apache.qpid.server.virtualhost.ReservedExchangeNameException; +import org.apache.qpid.server.virtualhost.UnknownExchangeException; +import org.apache.qpid.server.virtualhost.VirtualHostListener; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.qpid.server.virtualhost.plugins.QueueExistsException; + +public final class VirtualHostAdapter extends AbstractAdapter implements VirtualHost, VirtualHostListener +{ + private static final Logger LOGGER = Logger.getLogger(VirtualHostAdapter.class); + + @SuppressWarnings("serial") + public static final Map ATTRIBUTE_TYPES = Collections.unmodifiableMap(new HashMap(){{ + put(NAME, String.class); + put(TYPE, String.class); + put(STORE_PATH, String.class); + put(STORE_TYPE, String.class); + put(CONFIG_PATH, String.class); + put(STATE, State.class); + }}); + + private org.apache.qpid.server.virtualhost.VirtualHost _virtualHost; + + private final Map _connectionAdapters = + new HashMap(); + + private final Map _queueAdapters = + new HashMap(); + + private final Map _exchangeAdapters = + new HashMap(); + private StatisticsAdapter _statistics; + private final Broker _broker; + private final List _aliases = new ArrayList(); + private StatisticsGatherer _brokerStatisticsGatherer; + + public VirtualHostAdapter(UUID id, Map attributes, Broker broker, StatisticsGatherer brokerStatisticsGatherer, TaskExecutor taskExecutor) + { + super(id, null, MapValueConverter.convert(attributes, ATTRIBUTE_TYPES, false), taskExecutor, false); + _broker = broker; + _brokerStatisticsGatherer = brokerStatisticsGatherer; + validateAttributes(); + addParent(Broker.class, broker); + } + + private void validateAttributes() + { + String name = getName(); + if (name == null || "".equals(name.trim())) + { + throw new IllegalConfigurationException("Virtual host name must be specified"); + } + + String configurationFile = (String) getAttribute(CONFIG_PATH); + String type = (String) getAttribute(TYPE); + + boolean invalidAttributes = false; + if (configurationFile == null) + { + if (type == null) + { + invalidAttributes = true; + } + else + { + validateAttributes(type); + } + }/* + else + { + if (type != null) + { + invalidAttributes = true; + } + + }*/ + if (invalidAttributes) + { + throw new IllegalConfigurationException("Please specify either the 'configPath' attribute or 'type' attributes"); + } + + // pre-load the configuration in order to validate + try + { + createVirtualHostConfiguration(name); + } + catch(ConfigurationException e) + { + throw new IllegalConfigurationException("Failed to validate configuration", e); + } + } + + private void validateAttributes(String type) + { + final VirtualHostFactory factory = VirtualHostFactory.FACTORIES.get(type); + if(factory == null) + { + throw new IllegalArgumentException("Unknown virtual host type '"+ type +"'. Valid types are: " + VirtualHostFactory.TYPES.get()); + } + factory.validateAttributes(getActualAttributes()); + + } + + private void populateExchanges() + { + Collection actualExchanges = + _virtualHost.getExchanges(); + + synchronized (_exchangeAdapters) + { + for(org.apache.qpid.server.exchange.Exchange exchange : actualExchanges) + { + if(!_exchangeAdapters.containsKey(exchange)) + { + _exchangeAdapters.put(exchange, new ExchangeAdapter(this,exchange)); + } + } + } + } + + + private void populateQueues() + { + Collection actualQueues = _virtualHost.getQueues(); + if ( actualQueues != null ) + { + synchronized(_queueAdapters) + { + for(AMQQueue queue : actualQueues) + { + if(!_queueAdapters.containsKey(queue)) + { + _queueAdapters.put(queue, new QueueAdapter(this, queue)); + } + } + } + } + } + + public Collection getAliases() + { + return Collections.unmodifiableCollection(_aliases); + } + + public Collection getConnections() + { + synchronized(_connectionAdapters) + { + return new ArrayList(_connectionAdapters.values()); + } + + } + + /** + * Retrieve the ConnectionAdapter instance keyed by the AMQConnectionModel from this VirtualHost. + * @param connection the AMQConnectionModel used to index the ConnectionAdapter. + * @return the requested ConnectionAdapter. + */ + ConnectionAdapter getConnectionAdapter(AMQConnectionModel connection) + { + synchronized (_connectionAdapters) + { + return _connectionAdapters.get(connection); + } + } + + public Collection getQueues() + { + synchronized(_queueAdapters) + { + return new ArrayList(_queueAdapters.values()); + } + } + + public Collection getExchanges() + { + synchronized (_exchangeAdapters) + { + return new ArrayList(_exchangeAdapters.values()); + } + } + + + public Exchange createExchange(Map attributes) + throws AccessControlException, IllegalArgumentException + { + attributes = new HashMap(attributes); + + String name = MapValueConverter.getStringAttribute(Exchange.NAME, attributes, null); + State state = MapValueConverter.getEnumAttribute(State.class, Exchange.STATE, attributes, State.ACTIVE); + boolean durable = MapValueConverter.getBooleanAttribute(Exchange.DURABLE, attributes, false); + LifetimePolicy lifetime = MapValueConverter.getEnumAttribute(LifetimePolicy.class, Exchange.LIFETIME_POLICY, attributes, LifetimePolicy.PERMANENT); + String type = MapValueConverter.getStringAttribute(Exchange.TYPE, attributes, null); + long ttl = MapValueConverter.getLongAttribute(Exchange.TIME_TO_LIVE, attributes, 0l); + + attributes.remove(Exchange.NAME); + attributes.remove(Exchange.STATE); + attributes.remove(Exchange.DURABLE); + attributes.remove(Exchange.LIFETIME_POLICY); + attributes.remove(Exchange.TYPE); + attributes.remove(Exchange.TIME_TO_LIVE); + + return createExchange(name, state, durable, lifetime, ttl, type, attributes); + } + + public Exchange createExchange(final String name, + final State initialState, + final boolean durable, + final LifetimePolicy lifetime, + final long ttl, + final String type, + final Map attributes) + throws AccessControlException, IllegalArgumentException + { + checkVHostStateIsActive(); + + try + { + String alternateExchange = null; + if(attributes.containsKey(Exchange.ALTERNATE_EXCHANGE)) + { + Object altExchangeObject = attributes.get(Exchange.ALTERNATE_EXCHANGE); + if(altExchangeObject instanceof Exchange) + { + alternateExchange = ((Exchange) altExchangeObject).getName(); + } + else if(altExchangeObject instanceof UUID) + { + for(Exchange ex : getExchanges()) + { + if(altExchangeObject.equals(ex.getId())) + { + alternateExchange = ex.getName(); + break; + } + } + } + else if(altExchangeObject instanceof String) + { + + for(Exchange ex : getExchanges()) + { + if(altExchangeObject.equals(ex.getName())) + { + alternateExchange = ex.getName(); + break; + } + } + if(alternateExchange == null) + { + try + { + UUID id = UUID.fromString(altExchangeObject.toString()); + for(Exchange ex : getExchanges()) + { + if(id.equals(ex.getId())) + { + alternateExchange = ex.getName(); + break; + } + } + } + catch(IllegalArgumentException e) + { + // ignore + } + + } + } + } + org.apache.qpid.server.exchange.Exchange exchange = _virtualHost.createExchange(null, + name, + type, + durable, + lifetime == LifetimePolicy.AUTO_DELETE, + alternateExchange); + synchronized (_exchangeAdapters) + { + return _exchangeAdapters.get(exchange); + } + + } + catch(ExchangeExistsException e) + { + throw new IllegalArgumentException("Exchange with name '" + name + "' already exists"); + } + catch(ReservedExchangeNameException e) + { + throw new UnsupportedOperationException("'" + name + "' is a reserved exchange name"); + } + catch(UnknownExchangeException e) + { + throw new IllegalArgumentException("Alternate Exchange with name '" + e.getExchangeName() + "' does not exist"); + } + catch(AMQException e) + { + throw new IllegalArgumentException(e); + } + } + + public Queue createQueue(Map attributes) + throws AccessControlException, IllegalArgumentException + { + attributes = new HashMap(attributes); + + if (attributes.containsKey(Queue.TYPE)) + { + String typeAttribute = MapValueConverter.getStringAttribute(Queue.TYPE, attributes, null); + QueueType queueType = null; + try + { + queueType = QueueType.valueOf(typeAttribute.toUpperCase()); + } + catch(Exception e) + { + throw new IllegalArgumentException("Unsupported queue type :" + typeAttribute); + } + if (queueType == QueueType.LVQ && attributes.get(Queue.LVQ_KEY) == null) + { + attributes.put(Queue.LVQ_KEY, AMQQueueFactory.QPID_DEFAULT_LVQ_KEY); + } + else if (queueType == QueueType.PRIORITY && attributes.get(Queue.PRIORITIES) == null) + { + attributes.put(Queue.PRIORITIES, 10); + } + else if (queueType == QueueType.SORTED && attributes.get(Queue.SORT_KEY) == null) + { + throw new IllegalArgumentException("Sort key is not specified for sorted queue"); + } + } + + if (attributes.containsKey(Queue.MESSAGE_GROUP_KEY)) + { + String key = MapValueConverter.getStringAttribute(Queue.MESSAGE_GROUP_KEY, attributes); + attributes.remove(Queue.MESSAGE_GROUP_KEY); + attributes.put(QueueArgumentsConverter.QPID_GROUP_HEADER_KEY, key); + } + + if (attributes.containsKey(Queue.MESSAGE_GROUP_SHARED_GROUPS)) + { + if(MapValueConverter.getBooleanAttribute(Queue.MESSAGE_GROUP_SHARED_GROUPS, attributes)) + { + attributes.remove(Queue.MESSAGE_GROUP_SHARED_GROUPS); + attributes.put(QueueArgumentsConverter.QPID_SHARED_MSG_GROUP, SimpleAMQQueue.SHARED_MSG_GROUP_ARG_VALUE); + } + } + + String name = MapValueConverter.getStringAttribute(Queue.NAME, attributes, null); + State state = MapValueConverter.getEnumAttribute(State.class, Queue.STATE, attributes, State.ACTIVE); + boolean durable = MapValueConverter.getBooleanAttribute(Queue.DURABLE, attributes, false); + LifetimePolicy lifetime = MapValueConverter.getEnumAttribute(LifetimePolicy.class, Queue.LIFETIME_POLICY, attributes, LifetimePolicy.PERMANENT); + long ttl = MapValueConverter.getLongAttribute(Queue.TIME_TO_LIVE, attributes, 0l); + boolean exclusive= MapValueConverter.getBooleanAttribute(Queue.EXCLUSIVE, attributes, false); + + attributes.remove(Queue.NAME); + attributes.remove(Queue.STATE); + attributes.remove(Queue.DURABLE); + attributes.remove(Queue.LIFETIME_POLICY); + attributes.remove(Queue.TIME_TO_LIVE); + + return createQueue(name, state, durable, exclusive, lifetime, ttl, attributes); + } + + public Queue createQueue(final String name, + final State initialState, + final boolean durable, + boolean exclusive, + final LifetimePolicy lifetime, + final long ttl, + final Map attributes) + throws AccessControlException, IllegalArgumentException + { + checkVHostStateIsActive(); + + String owner = null; + if(exclusive) + { + Principal authenticatedPrincipal = AuthenticatedPrincipal.getOptionalAuthenticatedPrincipalFromSubject(SecurityManager.getThreadSubject()); + if(authenticatedPrincipal != null) + { + owner = authenticatedPrincipal.getName(); + } + } + + final boolean autoDelete = lifetime == LifetimePolicy.AUTO_DELETE; + + try + { + + AMQQueue queue = + _virtualHost.createQueue(UUIDGenerator.generateQueueUUID(name, _virtualHost.getName()), name, + durable, owner, autoDelete, exclusive, autoDelete && exclusive, attributes); + + synchronized (_queueAdapters) + { + return _queueAdapters.get(queue); + } + + } + catch(QueueExistsException qe) + { + throw new IllegalArgumentException("Queue with name "+name+" already exists"); + } + catch(AMQException e) + { + throw new IllegalArgumentException(e); + } + + } + + public String getName() + { + return (String)getAttribute(NAME); + } + + public String setName(final String currentName, final String desiredName) + throws IllegalStateException, AccessControlException + { + throw new IllegalStateException(); + } + + + public String getType() + { + return (String)getAttribute(TYPE); + } + + public String setType(final String currentType, final String desiredType) + throws IllegalStateException, AccessControlException + { + throw new IllegalStateException(); + } + + + @Override + public State getActualState() + { + if (_virtualHost == null) + { + State state = (State)super.getAttribute(STATE); + if (state == null) + { + return State.INITIALISING; + } + return state; + } + else + { + org.apache.qpid.server.virtualhost.State implementationState = _virtualHost.getState(); + switch(implementationState) + { + case INITIALISING: + return State.INITIALISING; + case ACTIVE: + return State.ACTIVE; + case PASSIVE: + return State.REPLICA; + case STOPPED: + return State.STOPPED; + case ERRORED: + return State.ERRORED; + default: + throw new IllegalStateException("Unsupported state:" + implementationState); + } + } + } + + public boolean isDurable() + { + return true; + } + + public void setDurable(final boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; + } + + public LifetimePolicy setLifetimePolicy(final LifetimePolicy expected, final LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + public long getTimeToLive() + { + return 0; + } + + public long setTimeToLive(final long expected, final long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + public Statistics getStatistics() + { + return _statistics; + } + + @Override + public Collection getChildren(Class clazz) + { + if(clazz == Exchange.class) + { + return (Collection) getExchanges(); + } + else if(clazz == Queue.class) + { + return (Collection) getQueues(); + } + else if(clazz == Connection.class) + { + return (Collection) getConnections(); + } + else if(clazz == VirtualHostAlias.class) + { + return (Collection) getAliases(); + } + else + { + return Collections.emptySet(); + } + } + + @Override + public C addChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + if(childClass == Exchange.class) + { + createExchange(attributes); + + // return null to avoid double notification of VirtualHostMBean + // as we already notify it in the exchangeRegistered + return null; + } + else if(childClass == Queue.class) + { + createQueue(attributes); + + // return null to avoid double notification of VirtualHostMBean + // as we already notify it in the queueRegistered + return null; + } + else if(childClass == VirtualHostAlias.class) + { + throw new UnsupportedOperationException(); + } + else if(childClass == Connection.class) + { + throw new UnsupportedOperationException(); + } + throw new IllegalArgumentException("Cannot create a child of class " + childClass.getSimpleName()); + } + + public void exchangeRegistered(org.apache.qpid.server.exchange.Exchange exchange) + { + ExchangeAdapter adapter = null; + synchronized (_exchangeAdapters) + { + if(!_exchangeAdapters.containsKey(exchange)) + { + adapter = new ExchangeAdapter(this, exchange); + _exchangeAdapters.put(exchange, adapter); + + } + + } + if(adapter != null) + { + childAdded(adapter); + } + + } + + + public void exchangeUnregistered(org.apache.qpid.server.exchange.Exchange exchange) + { + ExchangeAdapter adapter; + synchronized (_exchangeAdapters) + { + adapter = _exchangeAdapters.remove(exchange); + + } + + if(adapter != null) + { + childRemoved(adapter); + } + } + + public void queueRegistered(AMQQueue queue) + { + QueueAdapter adapter = null; + synchronized (_queueAdapters) + { + if(!_queueAdapters.containsKey(queue)) + { + adapter = new QueueAdapter(this, queue); + _queueAdapters.put(queue, adapter); + + } + + } + if(adapter != null) + { + childAdded(adapter); + } + + } + + public void queueUnregistered(AMQQueue queue) + { + + QueueAdapter adapter; + synchronized (_queueAdapters) + { + adapter = _queueAdapters.remove(queue); + + } + + if(adapter != null) + { + childRemoved(adapter); + } + } + + public void connectionRegistered(AMQConnectionModel connection) + { + ConnectionAdapter adapter = null; + synchronized (_connectionAdapters) + { + if(!_connectionAdapters.containsKey(connection)) + { + adapter = new ConnectionAdapter(connection, getTaskExecutor()); + _connectionAdapters.put(connection, adapter); + + } + + } + if(adapter != null) + { + childAdded(adapter); + } + } + + public void connectionUnregistered(AMQConnectionModel connection) + { + + ConnectionAdapter adapter; + synchronized (_connectionAdapters) + { + adapter = _connectionAdapters.remove(connection); + + } + + if(adapter != null) + { + // Call getSessions() first to ensure that any SessionAdapter children are cleanly removed and any + // corresponding ConfigurationChangeListener childRemoved() callback is called for child SessionAdapters. + adapter.getSessions(); + + childRemoved(adapter); + } + } + + QueueAdapter getQueueAdapter(AMQQueue queue) + { + synchronized (_queueAdapters) + { + return _queueAdapters.get(queue); + } + } + + public Collection getExchangeTypes() + { + Collection> types = + _virtualHost.getExchangeTypes(); + + Collection exchangeTypes = new ArrayList(); + + for(ExchangeType type : types) + { + exchangeTypes.add(type.getType()); + } + return Collections.unmodifiableCollection(exchangeTypes); + } + + public void executeTransaction(TransactionalOperation op) + { + MessageStore store = _virtualHost.getMessageStore(); + final LocalTransaction txn = new LocalTransaction(store); + + op.withinTransaction(new Transaction() + { + public void dequeue(final QueueEntry entry) + { + if(entry.acquire()) + { + txn.dequeue(entry.getQueue(), entry.getMessage(), new ServerTransaction.Action() + { + public void postCommit() + { + entry.discard(); + } + + public void onRollback() + { + } + }); + } + } + + public void copy(QueueEntry entry, Queue queue) + { + final ServerMessage message = entry.getMessage(); + final AMQQueue toQueue = ((QueueAdapter)queue).getAMQQueue(); + + txn.enqueue(toQueue, message, new ServerTransaction.Action() + { + public void postCommit() + { + try + { + toQueue.enqueue(message); + } + catch(AMQException e) + { + throw new RuntimeException(e); + } + } + + public void onRollback() + { + } + }); + + } + + public void move(final QueueEntry entry, Queue queue) + { + final ServerMessage message = entry.getMessage(); + final AMQQueue toQueue = ((QueueAdapter)queue).getAMQQueue(); + if(entry.acquire()) + { + txn.enqueue(toQueue, message, + new ServerTransaction.Action() + { + + public void postCommit() + { + try + { + toQueue.enqueue(message); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + } + + public void onRollback() + { + entry.release(); + } + }); + txn.dequeue(entry.getQueue(), message, + new ServerTransaction.Action() + { + + public void postCommit() + { + entry.discard(); + } + + public void onRollback() + { + + } + }); + } + } + + }); + txn.commit(); + } + + org.apache.qpid.server.virtualhost.VirtualHost getVirtualHost() + { + return _virtualHost; + } + + @Override + public Object getAttribute(String name) + { + if(ID.equals(name)) + { + return getId(); + } + else if(STATE.equals(name)) + { + return getActualState(); + } + else if(DURABLE.equals(name)) + { + return isDurable(); + } + else if(LIFETIME_POLICY.equals(name)) + { + return LifetimePolicy.PERMANENT; + } + else if(TIME_TO_LIVE.equals(name)) + { + // TODO + } + else if(CREATED.equals(name)) + { + // TODO + } + else if(UPDATED.equals(name)) + { + // TODO + } + else if (_virtualHost != null) + { + return getAttributeFromVirtualHostImplementation(name); + } + return super.getAttribute(name); + } + + private Object getAttributeFromVirtualHostImplementation(String name) + { + if(SUPPORTED_EXCHANGE_TYPES.equals(name)) + { + List types = new ArrayList(); + for(@SuppressWarnings("rawtypes") ExchangeType type : _virtualHost.getExchangeTypes()) + { + types.add(type.getType()); + } + return Collections.unmodifiableCollection(types); + } + else if(SUPPORTED_QUEUE_TYPES.equals(name)) + { + // TODO + } + else if(QUEUE_DEAD_LETTER_QUEUE_ENABLED.equals(name)) + { + return _virtualHost.getConfiguration().isDeadLetterQueueEnabled(); + } + else if(HOUSEKEEPING_CHECK_PERIOD.equals(name)) + { + return _virtualHost.getConfiguration().getHousekeepingCheckPeriod(); + } + else if(QUEUE_MAXIMUM_DELIVERY_ATTEMPTS.equals(name)) + { + return _virtualHost.getConfiguration().getMaxDeliveryCount(); + } + else if(QUEUE_FLOW_CONTROL_SIZE_BYTES.equals(name)) + { + return _virtualHost.getConfiguration().getCapacity(); + } + else if(QUEUE_FLOW_RESUME_SIZE_BYTES.equals(name)) + { + return _virtualHost.getConfiguration().getFlowResumeCapacity(); + } + else if(STORE_TYPE.equals(name)) + { + return _virtualHost.getMessageStore().getStoreType(); + } + else if(STORE_PATH.equals(name)) + { + return _virtualHost.getMessageStore().getStoreLocation(); + } + else if(STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE.equals(name)) + { + return _virtualHost.getConfiguration().getTransactionTimeoutIdleClose(); + } + else if(STORE_TRANSACTION_IDLE_TIMEOUT_WARN.equals(name)) + { + return _virtualHost.getConfiguration().getTransactionTimeoutIdleWarn(); + } + else if(STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE.equals(name)) + { + return _virtualHost.getConfiguration().getTransactionTimeoutOpenClose(); + } + else if(STORE_TRANSACTION_OPEN_TIMEOUT_WARN.equals(name)) + { + return _virtualHost.getConfiguration().getTransactionTimeoutOpenWarn(); + } + else if(QUEUE_ALERT_REPEAT_GAP.equals(name)) + { + return _virtualHost.getConfiguration().getMinimumAlertRepeatGap(); + } + else if(QUEUE_ALERT_THRESHOLD_MESSAGE_AGE.equals(name)) + { + return _virtualHost.getConfiguration().getMaximumMessageAge(); + } + else if(QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE.equals(name)) + { + return _virtualHost.getConfiguration().getMaximumMessageSize(); + } + else if(QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES.equals(name)) + { + return _virtualHost.getConfiguration().getMaximumQueueDepth(); + } + else if(QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES.equals(name)) + { + return _virtualHost.getConfiguration().getMaximumMessageCount(); + } + return super.getAttribute(name); + } + + @Override + public Collection getAttributeNames() + { + return AVAILABLE_ATTRIBUTES; + } + + private void checkVHostStateIsActive() + { + if (!org.apache.qpid.server.virtualhost.State.ACTIVE.equals(_virtualHost.getState())) + { + throw new IllegalStateException("The virtual hosts state of " + _virtualHost.getState() + + " does not permit this operation."); + } + } + + + private static class VirtualHostStatisticsAdapter extends StatisticsAdapter + { + private final org.apache.qpid.server.virtualhost.VirtualHost _vhost; + + private static final Collection VHOST_STATS = Arrays.asList( + VirtualHost.QUEUE_COUNT, + VirtualHost.EXCHANGE_COUNT, + VirtualHost.CONNECTION_COUNT); + + public VirtualHostStatisticsAdapter(org.apache.qpid.server.virtualhost.VirtualHost virtualHost) + { + super(virtualHost); + _vhost = virtualHost; + } + + @Override + public Collection getStatisticNames() + { + Set stats = new HashSet(super.getStatisticNames()); + stats.addAll(VHOST_STATS); + return stats; + } + + @Override + public Object getStatistic(String name) + { + if(VirtualHost.QUEUE_COUNT.equals(name)) + { + return _vhost.getQueues().size(); + } + else if(VirtualHost.EXCHANGE_COUNT.equals(name)) + { + return _vhost.getExchanges().size(); + } + else if(VirtualHost.CONNECTION_COUNT.equals(name)) + { + return _vhost.getConnectionRegistry().getConnections().size(); + } + else + { + return super.getStatistic(name); + } + } + } + + + @Override + protected boolean setState(State currentState, State desiredState) + { + if (desiredState == State.ACTIVE) + { + try + { + activate(); + } + catch(RuntimeException e) + { + changeAttribute(STATE, State.INITIALISING, State.ERRORED); + if (_broker.isManagementMode()) + { + LOGGER.warn("Failed to activate virtual host: " + getName(), e); + } + else + { + throw e; + } + } + return true; + } + else if (desiredState == State.STOPPED) + { + if (_virtualHost != null) + { + try + { + _virtualHost.close(); + } + finally + { + _broker.getVirtualHostRegistry().unregisterVirtualHost(_virtualHost); + } + } + return true; + } + else if (desiredState == State.DELETED) + { + String hostName = getName(); + + if (hostName.equals(_broker.getAttribute(Broker.DEFAULT_VIRTUAL_HOST))) + { + throw new IntegrityViolationException("Cannot delete default virtual host '" + hostName + "'"); + } + if (_virtualHost != null) + { + if (_virtualHost.getState() == org.apache.qpid.server.virtualhost.State.ACTIVE) + { + setDesiredState(currentState, State.STOPPED); + } + + MessageStore ms = _virtualHost.getMessageStore(); + if (ms != null) + { + try + { + ms.onDelete(); + } + catch(Exception e) + { + LOGGER.warn("Exception occured on store deletion", e); + } + } + + _virtualHost = null; + } + setAttribute(VirtualHost.STATE, getActualState(), State.DELETED); + return true; + } + return false; + } + + private void activate() + { + VirtualHostRegistry virtualHostRegistry = _broker.getVirtualHostRegistry(); + String virtualHostName = getName(); + try + { + VirtualHostConfiguration configuration = createVirtualHostConfiguration(virtualHostName); + String type = configuration.getType(); + final VirtualHostFactory factory = VirtualHostFactory.FACTORIES.get(type); + if(factory == null) + { + throw new IllegalArgumentException("Unknown virtual host type: " + type); + } + else + { + _virtualHost = factory.createVirtualHost(_broker.getVirtualHostRegistry(), + _brokerStatisticsGatherer, + _broker.getSecurityManager(), + configuration, + this); + } + } + catch (Exception e) + { + throw new RuntimeException("Failed to create virtual host " + virtualHostName, e); + } + + virtualHostRegistry.registerVirtualHost(_virtualHost); + + _statistics = new VirtualHostStatisticsAdapter(_virtualHost); + _virtualHost.addVirtualHostListener(this); + populateQueues(); + populateExchanges(); + + synchronized(_aliases) + { + for(Port port :_broker.getPorts()) + { + if (Protocol.hasAmqpProtocol(port.getProtocols())) + { + _aliases.add(new VirtualHostAliasAdapter(this, port)); + } + } + } + } + + private VirtualHostConfiguration createVirtualHostConfiguration(String virtualHostName) throws ConfigurationException + { + VirtualHostConfiguration configuration; + String configurationFile = (String)getAttribute(CONFIG_PATH); + if (configurationFile == null) + { + final MyConfiguration basicConfiguration = new MyConfiguration(); + PropertiesConfiguration config = new PropertiesConfiguration(); + final String type = (String) getAttribute(TYPE); + config.addProperty("type", type); + VirtualHostFactory factory = VirtualHostFactory.FACTORIES.get(type); + if(factory != null) + { + for(Map.Entry entry : factory.createVirtualHostConfiguration(this).entrySet()) + { + config.addProperty(entry.getKey(), entry.getValue()); + } + } + basicConfiguration.addConfiguration(config); + + CompositeConfiguration compositeConfiguration = new CompositeConfiguration(); + compositeConfiguration.addConfiguration(new SystemConfiguration()); + compositeConfiguration.addConfiguration(basicConfiguration); + configuration = new VirtualHostConfiguration(virtualHostName, compositeConfiguration , _broker); + } + else + { + if (!new File(configurationFile).exists()) + { + throw new IllegalConfigurationException("Configuration file '" + configurationFile + "' does not exist"); + } + configuration = new VirtualHostConfiguration(virtualHostName, new File(configurationFile) , _broker); + String type = configuration.getType(); + changeAttribute(TYPE,null,type); + VirtualHostFactory factory = VirtualHostFactory.FACTORIES.get(type); + if(factory != null) + { + for(Map.Entry entry : factory.convertVirtualHostConfiguration(configuration.getConfig()).entrySet()) + { + changeAttribute(entry.getKey(), getAttribute(entry.getKey()), entry.getValue()); + } + } + + } + return configuration; + } + + @Override + public SecurityManager getSecurityManager() + { + return _virtualHost.getSecurityManager(); + } + + @Override + public MessageStore getMessageStore() + { + return _virtualHost.getMessageStore(); + } + + @Override + protected void changeAttributes(Map attributes) + { + throw new UnsupportedOperationException("Changing attributes on virtualhosts is not supported."); + } + + @Override + protected void authoriseSetDesiredState(State currentState, State desiredState) throws AccessControlException + { + if(desiredState == State.DELETED) + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), VirtualHost.class, Operation.DELETE)) + { + throw new AccessControlException("Deletion of virtual host is denied"); + } + } + } + + @Override + protected void authoriseSetAttribute(String name, Object expected, Object desired) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), VirtualHost.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of virtual host attributes is denied"); + } + } + + @Override + protected void authoriseSetAttributes(Map attributes) throws AccessControlException + { + if (!_broker.getSecurityManager().authoriseConfiguringBroker(getName(), VirtualHost.class, Operation.UPDATE)) + { + throw new AccessControlException("Setting of virtual host attributes is denied"); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAliasAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAliasAdapter.java new file mode 100644 index 0000000000..91b705b004 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/model/adapter/VirtualHostAliasAdapter.java @@ -0,0 +1,150 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.model.adapter; + +import java.util.Map; +import org.apache.qpid.server.model.AuthenticationMethod; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.Statistics; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.VirtualHostAlias; + +import java.security.AccessControlException; +import java.util.Collection; +import java.util.Collections; + +public class VirtualHostAliasAdapter extends AbstractAdapter implements VirtualHostAlias +{ + private VirtualHostAdapter _vhost; + private Port _port; + + public VirtualHostAliasAdapter(VirtualHostAdapter virtualHostAdapter, Port port) + { + super(UUIDGenerator.generateVhostAliasUUID(virtualHostAdapter.getName(), port.getName()), virtualHostAdapter.getTaskExecutor()); + _vhost = virtualHostAdapter; + _port = port; + } + + @Override + public Port getPort() + { + return _port; + } + + @Override + public VirtualHost getVirtualHost() + { + return _vhost; + } + + @Override + public Collection getAuthenticationMethods() + { + return Collections.emptySet(); // TODO - Implement + } + + @Override + public String getName() + { + return _vhost.getName(); + } + + @Override + public String setName(String currentName, String desiredName) throws IllegalStateException, AccessControlException + { + throw new IllegalStateException(); // TODO - Implement + } + + @Override + public State getActualState() + { + return State.ACTIVE; // TODO - Implement + } + + @Override + public boolean isDurable() + { + return true; // TODO - Implement + } + + @Override + public void setDurable(boolean durable) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); + } + + @Override + public LifetimePolicy getLifetimePolicy() + { + return LifetimePolicy.PERMANENT; // TODO - Implement + } + + @Override + public LifetimePolicy setLifetimePolicy(LifetimePolicy expected, LifetimePolicy desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); // TODO - Implement + } + + @Override + public long getTimeToLive() + { + return 0; // TODO - Implement + } + + @Override + public long setTimeToLive(long expected, long desired) + throws IllegalStateException, AccessControlException, IllegalArgumentException + { + throw new IllegalStateException(); // TODO - Implement + } + + @Override + public Statistics getStatistics() + { + return NoStatistics.getInstance(); + } + + @Override + public Collection getChildren(Class clazz) + { + return Collections.emptySet(); + } + + @Override + public C createChild(Class childClass, Map attributes, ConfiguredObject... otherParents) + { + throw new UnsupportedOperationException(); + } + + @Override + protected boolean setState(State currentState, State desiredState) + { + // TODO: state is not supported at the moment + return false; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/AccessControlFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/AccessControlFactory.java new file mode 100644 index 0000000000..0298789672 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/AccessControlFactory.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.plugin; + +import java.util.Collection; +import java.util.Map; + +import org.apache.qpid.server.model.AccessControlProvider; +import org.apache.qpid.server.security.AccessControl; + +public interface AccessControlFactory extends Pluggable +{ + public static final String ATTRIBUTE_TYPE = AccessControlProvider.TYPE; + + AccessControl createInstance(Map attributes); + + /** + * Returns the access control provider type + * @return authentication provider type + */ + String getType(); + + /** + * Get the names of attributes of the access control which can be passed into + * {@link #createInstance(Map)} to create the group manager + * + * @return the collection of attribute names + */ + Collection getAttributeNames(); + + /** + * @return returns human readable descriptions for the attributes + */ + Map getAttributeDescriptions(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/AuthenticationManagerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/AuthenticationManagerFactory.java new file mode 100644 index 0000000000..e183370870 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/AuthenticationManagerFactory.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.plugin; + +import java.util.Collection; +import java.util.Map; + +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; + +public interface AuthenticationManagerFactory extends Pluggable +{ + public static final String ATTRIBUTE_TYPE = AuthenticationProvider.TYPE; + + /** + * Returns the authentication provider type + * @return authentication provider type + */ + String getType(); + + /** + * Creates authentication manager from the provided attributes + * + * @param attributes + * attributes to create authentication manager + * @return authentication manager instance + */ + AuthenticationManager createInstance(Map attributes); + + /** + * Get the names of attributes the authentication manager which can be passed into {@link #createInstance(Map)} to create the + * authentication manager + * + * @return the collection of attribute names + */ + Collection getAttributeNames(); + + /** + * @return returns human readable descriptions for the attributes + */ + Map getAttributeDescriptions(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ConfigurationStoreFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ConfigurationStoreFactory.java new file mode 100644 index 0000000000..382c742161 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ConfigurationStoreFactory.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.plugin; + +import java.util.Map; + +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.IllegalConfigurationException; + + +public interface ConfigurationStoreFactory extends Pluggable +{ + /** + * Returns the type of the store this factory can create + */ + public String getType(); + + /** + * Creates and opens the store from a given location using initial store if provided. + *

+ * If location does not exist, or the overwrite option is specified, then a new store is created from the initial store if it is provided + * + * @param storeLocation store location + * @param initialStore initial store + * @param overwrite overwrite existing store with initial store + * @param configProperties a map of configuration properties the store can use to resolve configuration variables + * @throws IllegalConfigurationException if store cannot be opened in the given location + */ + public ConfigurationEntryStore createStore(String storeLocation, ConfigurationEntryStore initialStore, boolean overwrite, Map configProperties); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/DurableConfigurationStoreFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/DurableConfigurationStoreFactory.java new file mode 100644 index 0000000000..94a029ced3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/DurableConfigurationStoreFactory.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.plugin; + +import java.util.Map; +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.MessageStore; + +public interface DurableConfigurationStoreFactory extends Pluggable +{ + String getType(); + + DurableConfigurationStore createDurableConfigurationStore(); + + void validateAttributes(Map attributes); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ExchangeType.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ExchangeType.java new file mode 100644 index 0000000000..ab19fa196e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ExchangeType.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.plugin; + +import java.util.UUID; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public interface ExchangeType extends Pluggable +{ + public String getType(); + + public T newInstance(UUID id, VirtualHost host, String name, + boolean durable, boolean autoDelete) throws AMQException; + + public String getDefaultExchangeName(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/GroupManagerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/GroupManagerFactory.java new file mode 100644 index 0000000000..3d7a6323eb --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/GroupManagerFactory.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.plugin; + +import java.util.Collection; +import java.util.Map; + +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.security.group.GroupManager; + +public interface GroupManagerFactory extends Pluggable +{ + public static final String ATTRIBUTE_TYPE = GroupProvider.TYPE; + + GroupManager createInstance(Map attributes); + + /** + * Returns the authentication provider type + * @return authentication provider type + */ + String getType(); + + /** + * Get the names of attributes the group manager which can be passed into {@link #createInstance(Map)} to create the + * group manager + * + * @return the collection of attribute names + */ + Collection getAttributeNames(); + + /** + * @return returns human readable descriptions for the attributes + */ + Map getAttributeDescriptions(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/JDBCConnectionProviderFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/JDBCConnectionProviderFactory.java new file mode 100644 index 0000000000..12fb9224bb --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/JDBCConnectionProviderFactory.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.plugin; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.store.jdbc.ConnectionProvider; + +public interface JDBCConnectionProviderFactory extends Pluggable +{ + String getType(); + + ConnectionProvider getConnectionProvider(String connectionUrl, VirtualHost virtualHost) + throws SQLException; + + static final class TYPES + { + private TYPES() + { + } + + public static Collection get() + { + QpidServiceLoader qpidServiceLoader = new QpidServiceLoader(); + Iterable factories = qpidServiceLoader.atLeastOneInstanceOf(JDBCConnectionProviderFactory.class); + List names = new ArrayList(); + for(JDBCConnectionProviderFactory factory : factories) + { + names.add(factory.getType()); + } + return Collections.unmodifiableCollection(names); + } + } + + + static final class FACTORIES + { + private FACTORIES() + { + } + + public static JDBCConnectionProviderFactory get(String type) + { + QpidServiceLoader qpidServiceLoader = new QpidServiceLoader(); + Iterable factories = qpidServiceLoader.atLeastOneInstanceOf(JDBCConnectionProviderFactory.class); + for(JDBCConnectionProviderFactory factory : factories) + { + if(factory.getType().equals(type)) + { + return factory; + } + } + return null; + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageConverter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageConverter.java new file mode 100644 index 0000000000..cf3860ba92 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageConverter.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.plugin; + +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public interface MessageConverter extends Pluggable +{ + Class getInputClass(); + Class getOutputClass(); + + N convert(M message, VirtualHost vhost); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageMetaDataType.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageMetaDataType.java new file mode 100644 index 0000000000..ee89782307 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageMetaDataType.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.plugin; + +import java.nio.ByteBuffer; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.store.StorableMessageMetaData; +import org.apache.qpid.server.store.StoredMessage; + +public interface MessageMetaDataType extends Pluggable +{ + + public static interface Factory + { + M createMetaData(ByteBuffer buf); + } + + public int ordinal(); + + public M createMetaData(ByteBuffer buf); + + public ServerMessage createMessage(StoredMessage msg); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageStoreFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageStoreFactory.java new file mode 100644 index 0000000000..81404dcba8 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/MessageStoreFactory.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.plugin; + +import java.util.Map; +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.store.MessageStore; + +public interface MessageStoreFactory extends Pluggable +{ + String getType(); + + MessageStore createMessageStore(); + + public Map convertStoreConfiguration(Configuration configuration); + + void validateAttributes(Map attributes); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/Pluggable.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/Pluggable.java new file mode 100644 index 0000000000..cc18e83f8e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/Pluggable.java @@ -0,0 +1,25 @@ +package org.apache.qpid.server.plugin;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +public interface Pluggable +{ + String getType(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PluginFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PluginFactory.java new file mode 100644 index 0000000000..7f804781a5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PluginFactory.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.plugin; + +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Plugin; + +public interface PluginFactory extends Pluggable +{ + static final String PLUGIN_TYPE = "pluginType"; + + Plugin createInstance(UUID id, Map attributes, Broker broker); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PreferencesProviderFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PreferencesProviderFactory.java new file mode 100644 index 0000000000..5a95b88591 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/PreferencesProviderFactory.java @@ -0,0 +1,12 @@ +package org.apache.qpid.server.plugin; + +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.PreferencesProvider; + +public interface PreferencesProviderFactory extends Pluggable +{ + PreferencesProvider createInstance(UUID id, Map attributes, AuthenticationProvider authenticationProvider); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ProtocolEngineCreator.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ProtocolEngineCreator.java new file mode 100644 index 0000000000..2fa9084b8b --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/ProtocolEngineCreator.java @@ -0,0 +1,35 @@ +package org.apache.qpid.server.plugin;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.AmqpProtocolVersion; +import org.apache.qpid.transport.network.NetworkConnection; + +public interface ProtocolEngineCreator extends Pluggable +{ + AmqpProtocolVersion getVersion(); + byte[] getHeaderIdentifier(); + ServerProtocolEngine newProtocolEngine(Broker broker, NetworkConnection network, Port port, Transport transport, long id); +} + diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/QpidServiceLoader.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/QpidServiceLoader.java new file mode 100644 index 0000000000..148a306b27 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/QpidServiceLoader.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.plugin; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ServiceLoader; + +import org.apache.log4j.Logger; + +/** + * Simple facade over a {@link ServiceLoader} to instantiate all configured implementations of an interface. + */ +public class QpidServiceLoader +{ + private static final Logger _logger = Logger.getLogger(QpidServiceLoader.class); + + public Iterable instancesOf(Class clazz) + { + return instancesOf(clazz, false); + } + + /** + * @throws RuntimeException if at least one implementation is not found. + */ + public Iterable atLeastOneInstanceOf(Class clazz) + { + return instancesOf(clazz, true); + } + + private Iterable instancesOf(Class clazz, boolean atLeastOne) + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Iterator serviceLoaderIterator = ServiceLoader.load(clazz, classLoader).iterator(); + + // create a new list so we can log the count + List serviceImplementations = new ArrayList(); + while(serviceLoaderIterator.hasNext()) + { + serviceImplementations.add(serviceLoaderIterator.next()); + } + + if(atLeastOne && serviceImplementations.isEmpty()) + { + throw new RuntimeException("At least one implementation of " + clazz + " expected"); + } + + if(_logger.isDebugEnabled()) + { + _logger.debug("Found " + serviceImplementations.size() + " implementations of " + clazz); + } + + return serviceImplementations; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/VirtualHostFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/VirtualHostFactory.java new file mode 100644 index 0000000000..9549b70c83 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/plugin/VirtualHostFactory.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.plugin; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.model.adapter.VirtualHostAdapter; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public interface VirtualHostFactory extends Pluggable +{ + String getType(); + + VirtualHost createVirtualHost(VirtualHostRegistry virtualHostRegistry, + StatisticsGatherer brokerStatisticsGatherer, + SecurityManager parentSecurityManager, + VirtualHostConfiguration hostConfig, + org.apache.qpid.server.model.VirtualHost virtualHost) throws Exception; + + void validateAttributes(Map attributes); + + Map createVirtualHostConfiguration(VirtualHostAdapter virtualHostAdapter); + + Map convertVirtualHostConfiguration(Configuration configuration); + + static final class TYPES + { + private TYPES() + { + } + + public static Collection get() + { + QpidServiceLoader qpidServiceLoader = new QpidServiceLoader(); + Iterable factories = qpidServiceLoader.atLeastOneInstanceOf(VirtualHostFactory.class); + List names = new ArrayList(); + for(VirtualHostFactory factory : factories) + { + names.add(factory.getType()); + } + return Collections.unmodifiableCollection(names); + } + } + + + static final class FACTORIES + { + private FACTORIES() + { + } + + public static VirtualHostFactory get(String type) + { + QpidServiceLoader qpidServiceLoader = new QpidServiceLoader(); + Iterable factories = qpidServiceLoader.atLeastOneInstanceOf(VirtualHostFactory.class); + for(VirtualHostFactory factory : factories) + { + if(factory.getType().equals(type)) + { + return factory; + } + } + return null; + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AMQConnectionModel.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AMQConnectionModel.java new file mode 100644 index 0000000000..19c5d03e0c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AMQConnectionModel.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.protocol; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.stats.StatisticsGatherer; + +import java.util.List; +import java.util.UUID; + +public interface AMQConnectionModel extends StatisticsGatherer +{ + /** + * Close the underlying Connection + * + * @param cause + * @param message + * @throws org.apache.qpid.AMQException + */ + public void close(AMQConstant cause, String message) throws AMQException; + + public void block(); + + public void unblock(); + + /** + * Close the given requested Session + * + * @param session + * @param cause + * @param message + * @throws org.apache.qpid.AMQException + */ + public void closeSession(AMQSessionModel session, AMQConstant cause, String message) throws AMQException; + + public long getConnectionId(); + + /** + * Get a list of all sessions using this connection. + * + * @return a list of {@link AMQSessionModel}s + */ + public List getSessionModels(); + + /** + * Return a {@link LogSubject} for the connection. + */ + public LogSubject getLogSubject(); + + public String getUserName(); + + public boolean isSessionNameUnique(byte[] name); + + String getRemoteAddressString(); + + String getClientId(); + + String getClientVersion(); + + String getPrincipalAsString(); + + long getSessionCountLimit(); + + long getLastIoTime(); + + Port getPort(); + + Transport getTransport(); + + void stop(); + + boolean isStopped(); + + String getVirtualHostName(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AMQSessionModel.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AMQSessionModel.java new file mode 100644 index 0000000000..a3833eebb9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AMQSessionModel.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.protocol; + +import java.util.UUID; +import java.util.concurrent.ConcurrentSkipListSet; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.SimpleAMQQueue; + +/** + * Session model interface. + * Extends {@link Comparable} to allow objects to be inserted into a {@link ConcurrentSkipListSet} + * when monitoring the blocking and blocking of queues/sessions in {@link SimpleAMQQueue}. + */ +public interface AMQSessionModel extends Comparable +{ + public UUID getId(); + + public AMQConnectionModel getConnectionModel(); + + public String getClientID(); + + public void close() throws AMQException; + + public void close(AMQConstant cause, String message) throws AMQException; + + public LogSubject getLogSubject(); + + /** + * This method is called from the housekeeping thread to check the status of + * transactions on this session and react appropriately. + * + * If a transaction is open for too long or idle for too long then a warning + * is logged or the connection is closed, depending on the configuration. An open + * transaction is one that has recent activity. The transaction age is counted + * from the time the transaction was started. An idle transaction is one that + * has had no activity, such as publishing or acknowledging messages. + * + * @param openWarn time in milliseconds before alerting on open transaction + * @param openClose time in milliseconds before closing connection with open transaction + * @param idleWarn time in milliseconds before alerting on idle transaction + * @param idleClose time in milliseconds before closing connection with idle transaction + */ + public void checkTransactionStatus(long openWarn, long openClose, long idleWarn, long idleClose) throws AMQException; + + void block(AMQQueue queue); + + void unblock(AMQQueue queue); + + void block(); + + void unblock(); + + boolean getBlocking(); + + boolean onSameConnection(InboundMessage inbound); + + int getUnacknowledgedMessageCount(); + + Long getTxnCount(); + Long getTxnStart(); + Long getTxnCommits(); + Long getTxnRejects(); + + int getChannelId(); + + int getConsumerCount(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AmqpProtocolVersion.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AmqpProtocolVersion.java new file mode 100644 index 0000000000..0a71fe257a --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/AmqpProtocolVersion.java @@ -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. + * + */ +package org.apache.qpid.server.protocol; + +public enum AmqpProtocolVersion { v0_8, v0_9, v0_9_1, v0_10, v1_0_0 } diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/LinkModel.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/LinkModel.java new file mode 100644 index 0000000000..16120a3523 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/LinkModel.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.protocol; + +public interface LinkModel +{ +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/LinkRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/LinkRegistry.java new file mode 100644 index 0000000000..67d6e9f8d1 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/LinkRegistry.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.protocol; + +import java.util.HashMap; +import java.util.Map; + +public class LinkRegistry +{ + private final Map _sendingLinks = new HashMap(); + private final Map _receivingLinks = new HashMap(); + + public synchronized LinkModel getDurableSendingLink(String name) + { + return _sendingLinks.get(name); + } + + public synchronized boolean registerSendingLink(String name, LinkModel link) + { + if(_sendingLinks.containsKey(name)) + { + return false; + } + else + { + _sendingLinks.put(name, link); + return true; + } + } + + public synchronized boolean unregisterSendingLink(String name) + { + if(!_sendingLinks.containsKey(name)) + { + return false; + } + else + { + _sendingLinks.remove(name); + return true; + } + } + + public synchronized LinkModel getDurableReceivingLink(String name) + { + return _receivingLinks.get(name); + } + + public synchronized boolean registerReceivingLink(String name, LinkModel link) + { + if(_receivingLinks.containsKey(name)) + { + return false; + } + else + { + _receivingLinks.put(name, link); + return true; + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MessageConverterRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MessageConverterRegistry.java new file mode 100644 index 0000000000..81e5af179d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MessageConverterRegistry.java @@ -0,0 +1,59 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.protocol; + +import java.util.HashMap; +import java.util.Map; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.plugin.MessageConverter; +import org.apache.qpid.server.plugin.QpidServiceLoader; + +public class MessageConverterRegistry +{ + private static Map, Map, MessageConverter>> _converters = + new HashMap, Map, MessageConverter>>(); + + static + { + + for(MessageConverter converter : (new QpidServiceLoader()).instancesOf(MessageConverter.class)) + { + Map, MessageConverter> map = _converters.get(converter.getInputClass()); + if(map == null) + { + map = new HashMap, MessageConverter>(); + _converters.put(converter.getInputClass(), map); + } + map.put(converter.getOutputClass(),converter); + } + } + + public static MessageConverter getConverter(Class from, Class to) + { + Map, MessageConverter> map = _converters.get(from); + if(map == null) + { + map = _converters.get(ServerMessage.class); + } + return map == null ? null : map.get(to); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java new file mode 100755 index 0000000000..47b578c4ef --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java @@ -0,0 +1,680 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.protocol; + + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.security.Principal; +import java.util.Set; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLPeerUnverifiedException; +import org.apache.log4j.Logger; +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ConnectionMessages; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.plugin.ProtocolEngineCreator; +import org.apache.qpid.transport.Sender; +import org.apache.qpid.transport.network.NetworkConnection; +import org.apache.qpid.transport.network.security.SSLStatus; +import org.apache.qpid.transport.network.security.ssl.SSLBufferingSender; +import org.apache.qpid.transport.network.security.ssl.SSLReceiver; + +public class MultiVersionProtocolEngine implements ServerProtocolEngine +{ + private static final Logger _logger = Logger.getLogger(MultiVersionProtocolEngine.class); + + private final long _id; + private final SSLContext _sslContext; + private final boolean _wantClientAuth; + private final boolean _needClientAuth; + private final Port _port; + private final Transport _transport; + private final ProtocolEngineCreator[] _creators; + + private Set _supported; + private String _fqdn; + private final Broker _broker; + private NetworkConnection _network; + private Sender _sender; + private final AmqpProtocolVersion _defaultSupportedReply; + + private volatile ServerProtocolEngine _delegate = new SelfDelegateProtocolEngine(); + + public MultiVersionProtocolEngine(final Broker broker, + SSLContext sslContext, boolean wantClientAuth, boolean needClientAuth, + final Set supported, + final AmqpProtocolVersion defaultSupportedReply, + Port port, Transport transport, final long id, ProtocolEngineCreator[] creators) + { + if(defaultSupportedReply != null && !supported.contains(defaultSupportedReply)) + { + throw new IllegalArgumentException("The configured default reply (" + defaultSupportedReply + + ") to an unsupported protocol version initiation is itself not supported!"); + } + + _id = id; + _broker = broker; + _supported = supported; + _defaultSupportedReply = defaultSupportedReply; + _sslContext = sslContext; + _wantClientAuth = wantClientAuth; + _needClientAuth = needClientAuth; + _port = port; + _transport = transport; + _creators = creators; + } + + + public SocketAddress getRemoteAddress() + { + return _delegate.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _delegate.getLocalAddress(); + } + + public long getWrittenBytes() + { + return _delegate.getWrittenBytes(); + } + + public long getReadBytes() + { + return _delegate.getReadBytes(); + } + + public void closed() + { + _delegate.closed(); + } + + public void writerIdle() + { + _delegate.writerIdle(); + } + + public void readerIdle() + { + _delegate.readerIdle(); + } + + + public void received(ByteBuffer msg) + { + _delegate.received(msg); + } + + public void exception(Throwable t) + { + _delegate.exception(t); + } + + public long getConnectionId() + { + return _delegate.getConnectionId(); + } + + private static final int MINIMUM_REQUIRED_HEADER_BYTES = 8; + + public void setNetworkConnection(NetworkConnection networkConnection) + { + setNetworkConnection(networkConnection, networkConnection.getSender()); + } + + public void setNetworkConnection(NetworkConnection network, Sender sender) + { + _network = network; + SocketAddress address = _network.getLocalAddress(); + if (address instanceof InetSocketAddress) + { + _fqdn = ((InetSocketAddress) address).getHostName(); + } + else + { + throw new IllegalArgumentException("Unsupported socket address class: " + address); + } + _sender = sender; + } + + @Override + public long getLastReadTime() + { + return _delegate.getLastReadTime(); + } + + @Override + public long getLastWriteTime() + { + return _delegate.getLastWriteTime(); + } + + + + private class ClosedDelegateProtocolEngine implements ServerProtocolEngine + { + public SocketAddress getRemoteAddress() + { + return _network.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _network.getLocalAddress(); + } + + public long getWrittenBytes() + { + return 0; + } + + public long getReadBytes() + { + return 0; + } + + public void received(ByteBuffer msg) + { + _logger.error("Error processing incoming data, could not negotiate a common protocol"); + } + + public void exception(Throwable t) + { + _logger.error("Error establishing session", t); + } + + public void closed() + { + + } + + public void writerIdle() + { + + } + + public void readerIdle() + { + + } + + public void setNetworkConnection(NetworkConnection network, Sender sender) + { + + } + + @Override + public long getLastReadTime() + { + return 0; + } + + @Override + public long getLastWriteTime() + { + return 0; + } + + public long getConnectionId() + { + return _id; + } + } + + private class SelfDelegateProtocolEngine implements ServerProtocolEngine + { + private final ByteBuffer _header = ByteBuffer.allocate(MINIMUM_REQUIRED_HEADER_BYTES); + private long _lastReadTime; + + public SocketAddress getRemoteAddress() + { + return _network.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _network.getLocalAddress(); + } + + public long getWrittenBytes() + { + return 0; + } + + public long getReadBytes() + { + return 0; + } + + public void received(ByteBuffer msg) + { + + _lastReadTime = System.currentTimeMillis(); + ByteBuffer msgheader = msg.duplicate(); + if(_header.remaining() > msgheader.limit()) + { + msg.position(msg.limit()); + } + else + { + msgheader.limit(_header.remaining()); + msg.position(_header.remaining()); + } + + _header.put(msgheader); + + if(!_header.hasRemaining()) + { + _header.flip(); + byte[] headerBytes = new byte[MINIMUM_REQUIRED_HEADER_BYTES]; + _header.get(headerBytes); + + + ServerProtocolEngine newDelegate = null; + byte[] supportedReplyBytes = null; + byte[] defaultSupportedReplyBytes = null; + AmqpProtocolVersion supportedReplyVersion = null; + + //Check the supported versions for a header match, and if there is one save the + //delegate. Also save most recent supported version and associated reply header bytes + for(int i = 0; newDelegate == null && i < _creators.length; i++) + { + if(_supported.contains(_creators[i].getVersion())) + { + supportedReplyBytes = _creators[i].getHeaderIdentifier(); + supportedReplyVersion = _creators[i].getVersion(); + byte[] compareBytes = _creators[i].getHeaderIdentifier(); + boolean equal = true; + for(int j = 0; equal && j sender) + { + + } + + @Override + public long getLastReadTime() + { + return _lastReadTime; + } + + @Override + public long getLastWriteTime() + { + return 0; + } + } + + private class SslDelegateProtocolEngine implements ServerProtocolEngine + { + private final MultiVersionProtocolEngine _decryptEngine; + private final SSLEngine _engine; + private final SSLReceiver _sslReceiver; + private final SSLBufferingSender _sslSender; + private long _lastReadTime; + + private SslDelegateProtocolEngine() + { + + _decryptEngine = new MultiVersionProtocolEngine(_broker, null, false, false, _supported, + _defaultSupportedReply, _port, Transport.SSL, _id, _creators); + + _engine = _sslContext.createSSLEngine(); + _engine.setUseClientMode(false); + + if(_needClientAuth) + { + _engine.setNeedClientAuth(_needClientAuth); + } + else if(_wantClientAuth) + { + _engine.setWantClientAuth(_wantClientAuth); + } + + SSLStatus sslStatus = new SSLStatus(); + _sslReceiver = new SSLReceiver(_engine,_decryptEngine,sslStatus); + _sslSender = new SSLBufferingSender(_engine,_sender,sslStatus); + _decryptEngine.setNetworkConnection(new SSLNetworkConnection(_engine,_network, _sslSender)); + } + + @Override + public void received(ByteBuffer msg) + { + _lastReadTime = System.currentTimeMillis(); + _sslReceiver.received(msg); + _sslSender.send(); + _sslSender.flush(); + } + + @Override + public void setNetworkConnection(NetworkConnection network, Sender sender) + { + //TODO - Implement + } + + @Override + public SocketAddress getRemoteAddress() + { + return _decryptEngine.getRemoteAddress(); + } + + @Override + public SocketAddress getLocalAddress() + { + return _decryptEngine.getLocalAddress(); + } + + @Override + public long getWrittenBytes() + { + return _decryptEngine.getWrittenBytes(); + } + + @Override + public long getReadBytes() + { + return _decryptEngine.getReadBytes(); + } + + @Override + public void closed() + { + _decryptEngine.closed(); + } + + @Override + public void writerIdle() + { + _decryptEngine.writerIdle(); + } + + @Override + public void readerIdle() + { + _decryptEngine.readerIdle(); + } + + @Override + public void exception(Throwable t) + { + _decryptEngine.exception(t); + } + + @Override + public long getConnectionId() + { + return _decryptEngine.getConnectionId(); + } + + @Override + public long getLastReadTime() + { + return _lastReadTime; + } + + @Override + public long getLastWriteTime() + { + return _decryptEngine.getLastWriteTime(); + } + } + + private boolean looksLikeSSL(byte[] headerBytes) + { + return looksLikeSSLv3ClientHello(headerBytes) || looksLikeSSLv2ClientHello(headerBytes); + } + + private boolean looksLikeSSLv3ClientHello(byte[] headerBytes) + { + return headerBytes[0] == 22 && // SSL Handshake + (headerBytes[1] == 3 && // SSL 3.0 / TLS 1.x + (headerBytes[2] == 0 || // SSL 3.0 + headerBytes[2] == 1 || // TLS 1.0 + headerBytes[2] == 2 || // TLS 1.1 + headerBytes[2] == 3)) && // TLS1.2 + (headerBytes[5] == 1); // client_hello + } + + private boolean looksLikeSSLv2ClientHello(byte[] headerBytes) + { + return headerBytes[0] == -128 && + headerBytes[3] == 3 && // SSL 3.0 / TLS 1.x + (headerBytes[4] == 0 || // SSL 3.0 + headerBytes[4] == 1 || // TLS 1.0 + headerBytes[4] == 2 || // TLS 1.1 + headerBytes[4] == 3); + } + + + private static class SSLNetworkConnection implements NetworkConnection + { + private final NetworkConnection _network; + private final SSLBufferingSender _sslSender; + private final SSLEngine _engine; + + public SSLNetworkConnection(SSLEngine engine, NetworkConnection network, + SSLBufferingSender sslSender) + { + _engine = engine; + _network = network; + _sslSender = sslSender; + + } + + @Override + public Sender getSender() + { + return _sslSender; + } + + @Override + public void start() + { + _network.start(); + } + + @Override + public void close() + { + _sslSender.close(); + + _network.close(); + } + + @Override + public SocketAddress getRemoteAddress() + { + return _network.getRemoteAddress(); + } + + @Override + public SocketAddress getLocalAddress() + { + return _network.getLocalAddress(); + } + + @Override + public void setMaxWriteIdle(int sec) + { + _network.setMaxWriteIdle(sec); + } + + @Override + public void setMaxReadIdle(int sec) + { + _network.setMaxReadIdle(sec); + } + + @Override + public void setPeerPrincipal(Principal principal) + { + _network.setPeerPrincipal(principal); + } + + @Override + public Principal getPeerPrincipal() + { + try + { + return _engine.getSession().getPeerPrincipal(); + } + catch (SSLPeerUnverifiedException e) + { + return null; + } + } + + @Override + public int getMaxReadIdle() + { + return _network.getMaxReadIdle(); + } + + @Override + public int getMaxWriteIdle() + { + return _network.getMaxWriteIdle(); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngineFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngineFactory.java new file mode 100755 index 0000000000..3ce9383ee0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngineFactory.java @@ -0,0 +1,89 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.protocol; + +import java.util.ArrayList; +import java.util.List; +import javax.net.ssl.SSLContext; +import org.apache.qpid.protocol.ProtocolEngineFactory; +import org.apache.qpid.protocol.ServerProtocolEngine; +import org.apache.qpid.server.model.Broker; + +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.plugin.ProtocolEngineCreator; +import org.apache.qpid.server.plugin.QpidServiceLoader; + +public class MultiVersionProtocolEngineFactory implements ProtocolEngineFactory +{ + private static final AtomicLong ID_GENERATOR = new AtomicLong(0); + + private final Broker _broker; + private final Set _supported; + private final AmqpProtocolVersion _defaultSupportedReply; + private final SSLContext _sslContext; + private final boolean _wantClientAuth; + private final boolean _needClientAuth; + private final Port _port; + private final Transport _transport; + private final ProtocolEngineCreator[] _creators; + + public MultiVersionProtocolEngineFactory(Broker broker, + SSLContext sslContext, + boolean wantClientAuth, + boolean needClientAuth, + final Set supportedVersions, + final AmqpProtocolVersion defaultSupportedReply, + Port port, + Transport transport) + { + if(defaultSupportedReply != null && !supportedVersions.contains(defaultSupportedReply)) + { + throw new IllegalArgumentException("The configured default reply (" + defaultSupportedReply + + ") to an unsupported protocol version initiation is itself not supported!"); + } + + _broker = broker; + _sslContext = sslContext; + _supported = supportedVersions; + _defaultSupportedReply = defaultSupportedReply; + List creators = new ArrayList(); + for(ProtocolEngineCreator c : new QpidServiceLoader().instancesOf(ProtocolEngineCreator.class)) + { + creators.add(c); + } + _creators = creators.toArray(new ProtocolEngineCreator[creators.size()]); + _wantClientAuth = wantClientAuth; + _needClientAuth = needClientAuth; + _port = port; + _transport = transport; + } + + public ServerProtocolEngine newProtocolEngine() + { + return new MultiVersionProtocolEngine(_broker, _sslContext, _wantClientAuth, _needClientAuth, + _supported, _defaultSupportedReply, _port, _transport, + ID_GENERATOR.getAndIncrement(), + _creators); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java new file mode 100644 index 0000000000..46c2a635b7 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.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.virtualhost.VirtualHost; + +import java.util.Map; +import java.util.UUID; + +public class AMQPriorityQueue extends OutOfOrderQueue +{ + protected AMQPriorityQueue(UUID id, + final String name, + final boolean durable, + final String owner, + final boolean autoDelete, + boolean exclusive, + final VirtualHost virtualHost, + Map arguments, int priorities) + { + super(id, name, durable, owner, autoDelete, exclusive, virtualHost, new PriorityQueueList.Factory(priorities), arguments); + } + + public int getPriorities() + { + return ((PriorityQueueList) getEntries()).getPriorities(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQQueue.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQQueue.java new file mode 100644 index 0000000000..ceebe4f965 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQQueue.java @@ -0,0 +1,331 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.binding.Binding; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeReferrer; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.security.AuthorizationHolder; +import org.apache.qpid.server.store.TransactionLogResource; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface AMQQueue extends Comparable, ExchangeReferrer, TransactionLogResource, BaseQueue +{ + String getName(); + + public interface NotificationListener + { + void notifyClients(NotificationCheck notification, AMQQueue queue, String notificationMsg); + } + + boolean getDeleteOnNoConsumers(); + + void setDeleteOnNoConsumers(boolean b); + + void addBinding(Binding binding); + + void removeBinding(Binding binding); + + List getBindings(); + + int getBindingCount(); + + LogSubject getLogSubject(); + + long getUnackedMessageBytes(); + + long getTotalDequeueCount(); + + long getTotalEnqueueCount(); + + public interface Context + { + QueueEntry getLastSeenEntry(); + } + + void setNoLocal(boolean b); + + boolean isAutoDelete(); + + String getOwner(); + AuthorizationHolder getAuthorizationHolder(); + void setAuthorizationHolder(AuthorizationHolder principalHolder); + + void setExclusiveOwningSession(AMQSessionModel owner); + AMQSessionModel getExclusiveOwningSession(); + + VirtualHost getVirtualHost(); + + void registerSubscription(final Subscription subscription, final boolean exclusive) throws AMQException; + + void unregisterSubscription(final Subscription subscription) throws AMQException; + + Collection getConsumers(); + + interface SubscriptionRegistrationListener + { + void subscriptionRegistered(AMQQueue queue, Subscription subscription); + void subscriptionUnregistered(AMQQueue queue, Subscription subscription); + } + + void addSubscriptionRegistrationListener(SubscriptionRegistrationListener listener); + void removeSubscriptionRegistrationListener(SubscriptionRegistrationListener listener); + + + int getConsumerCount(); + + int getActiveConsumerCount(); + + boolean hasExclusiveSubscriber(); + + boolean isUnused(); + + boolean isEmpty(); + + int getMessageCount(); + + int getUndeliveredMessageCount(); + + + long getQueueDepth(); + + long getReceivedMessageCount(); + + long getOldestMessageArrivalTime(); + + boolean isDeleted(); + + int delete() throws AMQException; + + void requeue(QueueEntry entry); + + void dequeue(QueueEntry entry, Subscription sub); + + void decrementUnackedMsgCount(QueueEntry queueEntry); + + boolean resend(final QueueEntry entry, final Subscription subscription) throws AMQException; + + void addQueueDeleteTask(final Task task); + void removeQueueDeleteTask(final Task task); + + + + List getMessagesOnTheQueue(); + + List getMessagesOnTheQueue(long fromMessageId, long toMessageId); + + List getMessagesOnTheQueue(int num); + + List getMessagesOnTheQueue(int num, int offest); + + QueueEntry getMessageOnTheQueue(long messageId); + + /** + * Returns a list of QueEntries from a given range of queue positions, eg messages 5 to 10 on the queue. + * + * The 'queue position' index starts from 1. Using 0 in 'from' will be ignored and continue from 1. + * Using 0 in the 'to' field will return an empty list regardless of the 'from' value. + * @param fromPosition + * @param toPosition + * @return + */ + public List getMessagesRangeOnTheQueue(final long fromPosition, final long toPosition); + + void visit(QueueEntryVisitor visitor); + + + long getMaximumMessageSize(); + + void setMaximumMessageSize(long value); + + + long getMaximumMessageCount(); + + void setMaximumMessageCount(long value); + + + long getMaximumQueueDepth(); + + void setMaximumQueueDepth(long value); + + + long getMaximumMessageAge(); + + void setMaximumMessageAge(final long maximumMessageAge); + + + long getMinimumAlertRepeatGap(); + + void setMinimumAlertRepeatGap(long value); + + + long getCapacity(); + + void setCapacity(long capacity); + + + long getFlowResumeCapacity(); + + void setFlowResumeCapacity(long flowResumeCapacity); + + boolean isOverfull(); + + void deleteMessageFromTop(); + + long clearQueue() throws AMQException; + + /** + * Checks the status of messages on the queue, purging expired ones, firing age related alerts etc. + * @throws AMQException + */ + void checkMessageStatus() throws AMQException; + + Set getNotificationChecks(); + + void flushSubscription(final Subscription sub) throws AMQException; + + void deliverAsync(final Subscription sub); + + void deliverAsync(); + + void stop(); + + boolean isExclusive(); + + Exchange getAlternateExchange(); + + void setAlternateExchange(Exchange exchange); + + Collection getAvailableAttributes(); + Object getAttribute(String attrName); + + void checkCapacity(AMQSessionModel channel); + + /** + * ExistingExclusiveSubscription signals a failure to create a subscription, because an exclusive subscription + * already exists. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Represent failure to create a subscription, because an exclusive subscription already exists. + *
+ * + * @todo Not an AMQP exception as no status code. + * + * @todo Move to top level, used outside this class. + */ + static final class ExistingExclusiveSubscription extends AMQException + { + + public ExistingExclusiveSubscription() + { + super(""); + } + } + + /** + * ExistingSubscriptionPreventsExclusive signals a failure to create an exclusive subscription, as a subscription + * already exists. + * + *

+ *
CRC Card
Responsibilities Collaborations + *
Represent failure to create an exclusive subscription, as a subscription already exists. + *
+ * + * @todo Not an AMQP exception as no status code. + * + * @todo Move to top level, used outside this class. + */ + static final class ExistingSubscriptionPreventsExclusive extends AMQException + { + public ExistingSubscriptionPreventsExclusive() + { + super(""); + } + } + + static interface Task + { + public void doTask(AMQQueue queue) throws AMQException; + } + + void configure(QueueConfiguration config); + + void setExclusive(boolean exclusive); + + /** + * Gets the maximum delivery count. If a message on this queue + * is delivered more than maximumDeliveryCount, the message will be + * routed to the {@link #getAlternateExchange()} (if set), or otherwise + * discarded. 0 indicates that maximum deliver count should not be enforced. + * + * @return maximum delivery count + */ + int getMaximumDeliveryCount(); + + /** + * Sets the maximum delivery count. + * + * @param maximumDeliveryCount maximum delivery count + */ + public void setMaximumDeliveryCount(final int maximumDeliveryCount); + + void setNotificationListener(NotificationListener listener); + + /** + * Sets the free text description of this queue. + * + * @param description + * + */ + void setDescription(String description); + + /** + * Gets the free text description of this queue. + */ + String getDescription(); + + long getPersistentByteDequeues(); + + long getPersistentMsgDequeues(); + + long getPersistentByteEnqueues(); + + long getPersistentMsgEnqueues(); + + long getTotalDequeueSize(); + + long getTotalEnqueueSize(); + + long getUnackedMessageCount(); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java new file mode 100644 index 0000000000..029c7e4f86 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java @@ -0,0 +1,542 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.exchange.DefaultExchangeFactory; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.store.DurableConfigurationStoreHelper; +import org.apache.qpid.server.virtualhost.ExchangeExistsException; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class AMQQueueFactory implements QueueFactory +{ + public static final String QPID_DEFAULT_LVQ_KEY = "qpid.LVQ_key"; + + + public static final String DEFAULT_DLQ_NAME_SUFFIX = "_DLQ"; + public static final String DLQ_ROUTING_KEY = "dlq"; + private static final int MAX_LENGTH = 255; + + private final VirtualHost _virtualHost; + private final QueueRegistry _queueRegistry; + + public AMQQueueFactory(VirtualHost virtualHost, QueueRegistry queueRegistry) + { + _virtualHost = virtualHost; + _queueRegistry = queueRegistry; + } + + private abstract static class QueueProperty + { + + private final String _argumentName; + + + public QueueProperty(String argumentName) + { + _argumentName = argumentName; + } + + public String getArgumentName() + { + return _argumentName; + } + + + public abstract void setPropertyValue(AMQQueue queue, Object value); + + } + + private abstract static class QueueLongProperty extends QueueProperty + { + + public QueueLongProperty(String argumentName) + { + super(argumentName); + } + + public void setPropertyValue(AMQQueue queue, Object value) + { + if(value instanceof Number) + { + setPropertyValue(queue, ((Number)value).longValue()); + } + + } + + abstract void setPropertyValue(AMQQueue queue, long value); + + + } + + private abstract static class QueueIntegerProperty extends QueueProperty + { + public QueueIntegerProperty(String argumentName) + { + super(argumentName); + } + + public void setPropertyValue(AMQQueue queue, Object value) + { + if(value instanceof Number) + { + setPropertyValue(queue, ((Number)value).intValue()); + } + + } + abstract void setPropertyValue(AMQQueue queue, int value); + } + + private static final QueueProperty[] DECLAREABLE_PROPERTIES = { + new QueueLongProperty(Queue.ALERT_THRESHOLD_MESSAGE_AGE) + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setMaximumMessageAge(value); + } + }, + new QueueLongProperty(Queue.ALERT_THRESHOLD_MESSAGE_SIZE) + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setMaximumMessageSize(value); + } + }, + new QueueLongProperty(Queue.ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES) + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setMaximumMessageCount(value); + } + }, + new QueueLongProperty(Queue.ALERT_THRESHOLD_QUEUE_DEPTH_BYTES) + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setMaximumQueueDepth(value); + } + }, + new QueueLongProperty(Queue.ALERT_REPEAT_GAP) + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setMinimumAlertRepeatGap(value); + } + }, + new QueueLongProperty(Queue.QUEUE_FLOW_CONTROL_SIZE_BYTES) + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setCapacity(value); + } + }, + new QueueLongProperty(Queue.QUEUE_FLOW_RESUME_SIZE_BYTES) + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setFlowResumeCapacity(value); + } + }, + new QueueIntegerProperty(Queue.MAXIMUM_DELIVERY_ATTEMPTS) + { + public void setPropertyValue(AMQQueue queue, int value) + { + queue.setMaximumDeliveryCount(value); + } + } + }; + + @Override + public AMQQueue restoreQueue(UUID id, + String queueName, + String owner, + boolean autoDelete, + boolean exclusive, + boolean deleteOnNoConsumer, + Map arguments) throws AMQSecurityException, AMQException + { + return createOrRestoreQueue(id, queueName, true, owner, autoDelete, exclusive, deleteOnNoConsumer, arguments, false); + + } + + /** + * @param id the id to use. + * @param deleteOnNoConsumer + */ + @Override + public AMQQueue createQueue(UUID id, + String queueName, + boolean durable, + String owner, + boolean autoDelete, + boolean exclusive, + boolean deleteOnNoConsumer, + Map arguments) throws AMQSecurityException, AMQException + { + return createOrRestoreQueue(id, queueName, durable, owner, autoDelete, exclusive, deleteOnNoConsumer, arguments, true); + } + + private AMQQueue createOrRestoreQueue(UUID id, + String queueName, + boolean durable, + String owner, + boolean autoDelete, + boolean exclusive, + boolean deleteOnNoConsumer, + Map arguments, + boolean createInStore) throws AMQSecurityException, AMQException + { + if (id == null) + { + throw new IllegalArgumentException("Queue id must not be null"); + } + if (queueName == null) + { + throw new IllegalArgumentException("Queue name must not be null"); + } + + + QueueConfiguration queueConfiguration = _virtualHost.getConfiguration().getQueueConfiguration(queueName); + + boolean createDLQ = createDLQ(autoDelete, arguments, queueConfiguration); + if (createDLQ) + { + validateDLNames(queueName); + } + + int priorities = 1; + String conflationKey = null; + String sortingKey = null; + + if(arguments != null) + { + if(arguments.containsKey(Queue.LVQ_KEY)) + { + conflationKey = (String) arguments.get(Queue.LVQ_KEY); + if(conflationKey == null) + { + conflationKey = QPID_DEFAULT_LVQ_KEY; + } + } + else if(arguments.containsKey(Queue.PRIORITIES)) + { + Object prioritiesObj = arguments.get(Queue.PRIORITIES); + if(prioritiesObj instanceof Number) + { + priorities = ((Number)prioritiesObj).intValue(); + } + else if(prioritiesObj instanceof String) + { + try + { + priorities = Integer.parseInt(prioritiesObj.toString()); + } + catch (NumberFormatException e) + { + // TODO - should warn here of invalid format + } + } + else + { + // TODO - should warn here of invalid format + } + } + else if(arguments.containsKey(Queue.SORT_KEY)) + { + sortingKey = (String)arguments.get(Queue.SORT_KEY); + } + } + + AMQQueue q; + if(sortingKey != null) + { + q = new SortedQueue(id, queueName, durable, owner, autoDelete, exclusive, _virtualHost, arguments, sortingKey); + } + else if(conflationKey != null) + { + q = new ConflationQueue(id, queueName, durable, owner, autoDelete, exclusive, _virtualHost, arguments, conflationKey); + } + else if(priorities > 1) + { + q = new AMQPriorityQueue(id, queueName, durable, owner, autoDelete, exclusive, _virtualHost, arguments, priorities); + } + else + { + q = new SimpleAMQQueue(id, queueName, durable, owner, autoDelete, exclusive, _virtualHost, arguments); + } + + q.setDeleteOnNoConsumers(deleteOnNoConsumer); + + //Register the new queue + _queueRegistry.registerQueue(q); + + q.configure(_virtualHost.getConfiguration().getQueueConfiguration(queueName)); + + if(arguments != null) + { + for(QueueProperty p : DECLAREABLE_PROPERTIES) + { + if(arguments.containsKey(p.getArgumentName())) + { + p.setPropertyValue(q, arguments.get(p.getArgumentName())); + } + } + + if(arguments.get(Queue.NO_LOCAL) instanceof Boolean) + { + q.setNoLocal((Boolean)arguments.get(Queue.NO_LOCAL)); + } + + } + + if(createDLQ) + { + final String dlExchangeName = getDeadLetterExchangeName(queueName); + final String dlQueueName = getDeadLetterQueueName(queueName); + + Exchange dlExchange = null; + final UUID dlExchangeId = UUIDGenerator.generateExchangeUUID(dlExchangeName, _virtualHost.getName()); + + try + { + dlExchange = _virtualHost.createExchange(dlExchangeId, + dlExchangeName, + ExchangeDefaults.FANOUT_EXCHANGE_CLASS, + true, false, null); + } + catch(ExchangeExistsException e) + { + // We're ok if the exchange already exists + dlExchange = e.getExistingExchange(); + } + + AMQQueue dlQueue = null; + + synchronized(_queueRegistry) + { + dlQueue = _queueRegistry.getQueue(dlQueueName); + + if(dlQueue == null) + { + //set args to disable DLQ'ing/MDC from the DLQ itself, preventing loops etc + final Map args = new HashMap(); + args.put(Queue.CREATE_DLQ_ON_CREATION, false); + args.put(Queue.MAXIMUM_DELIVERY_ATTEMPTS, 0); + + dlQueue = _virtualHost.createQueue(UUIDGenerator.generateQueueUUID(dlQueueName, _virtualHost.getName()), dlQueueName, true, owner, false, exclusive, + false, args); + } + } + + //ensure the queue is bound to the exchange + if(!dlExchange.isBound(DLQ_ROUTING_KEY, dlQueue)) + { + //actual routing key used does not matter due to use of fanout exchange, + //but we will make the key 'dlq' as it can be logged at creation. + dlExchange.addBinding(DLQ_ROUTING_KEY, dlQueue, null); + } + q.setAlternateExchange(dlExchange); + } + else if(arguments != null && arguments.get(Queue.ALTERNATE_EXCHANGE) instanceof String) + { + + final String altExchangeAttr = (String) arguments.get(Queue.ALTERNATE_EXCHANGE); + Exchange altExchange; + try + { + altExchange = _virtualHost.getExchange(UUID.fromString(altExchangeAttr)); + } + catch(IllegalArgumentException e) + { + altExchange = _virtualHost.getExchange(altExchangeAttr); + } + q.setAlternateExchange(altExchange); + } + + if (createInStore && q.isDurable() && !q.isAutoDelete()) + { + DurableConfigurationStoreHelper.createQueue(_virtualHost.getDurableConfigurationStore(), q); + } + + return q; + } + + public AMQQueue createAMQQueueImpl(QueueConfiguration config) throws AMQException + { + String queueName = config.getName(); + + boolean durable = config.getDurable(); + boolean autodelete = config.getAutoDelete(); + boolean exclusive = config.getExclusive(); + String owner = config.getOwner(); + Map arguments = createQueueArgumentsFromConfig(config); + + // we need queues that are defined in config to have deterministic ids. + UUID id = UUIDGenerator.generateQueueUUID(queueName, _virtualHost.getName()); + + AMQQueue q = createQueue(id, queueName, durable, owner, autodelete, exclusive, false, arguments); + q.configure(config); + return q; + } + + /** + * Validates DLQ and DLE names + *

+ * DLQ name and DLQ exchange name need to be validated in order to keep + * integrity in cases when queue name passes validation check but DLQ name + * or DL exchange name fails to pass it. Otherwise, we might have situations + * when queue is created but DL exchange or/and DLQ creation fail. + *

+ * + * @param name + * queue name + * @throws IllegalArgumentException + * thrown if length of queue name or exchange name exceed 255 + */ + protected static void validateDLNames(String name) + { + // check if DLQ name and DLQ exchange name do not exceed 255 + String exchangeName = getDeadLetterExchangeName(name); + if (exchangeName.length() > MAX_LENGTH) + { + throw new IllegalArgumentException("DL exchange name '" + exchangeName + + "' length exceeds limit of " + MAX_LENGTH + " characters for queue " + name); + } + String queueName = getDeadLetterQueueName(name); + if (queueName.length() > MAX_LENGTH) + { + throw new IllegalArgumentException("DLQ queue name '" + queueName + "' length exceeds limit of " + + MAX_LENGTH + " characters for queue " + name); + } + } + + /** + * Checks if DLQ is enabled for the queue. + * + * @param autoDelete + * queue auto-delete flag + * @param arguments + * queue arguments + * @param qConfig + * queue configuration + * @return true if DLQ enabled + */ + protected static boolean createDLQ(boolean autoDelete, Map arguments, QueueConfiguration qConfig) + { + //feature is not to be enabled for temporary queues or when explicitly disabled by argument + if (!(autoDelete || (arguments != null && arguments.containsKey(Queue.ALTERNATE_EXCHANGE)))) + { + boolean dlqArgumentPresent = arguments != null + && arguments.containsKey(Queue.CREATE_DLQ_ON_CREATION); + if (dlqArgumentPresent || qConfig.isDeadLetterQueueEnabled()) + { + boolean dlqEnabled = true; + if (dlqArgumentPresent) + { + Object argument = arguments.get(Queue.CREATE_DLQ_ON_CREATION); + dlqEnabled = (argument instanceof Boolean && ((Boolean)argument).booleanValue()) + || (argument instanceof String && Boolean.parseBoolean(argument.toString())); + } + return dlqEnabled ; + } + } + return false; + } + + /** + * Generates a dead letter queue name for a given queue name + * + * @param name + * queue name + * @return DLQ name + */ + protected static String getDeadLetterQueueName(String name) + { + return name + System.getProperty(BrokerProperties.PROPERTY_DEAD_LETTER_QUEUE_SUFFIX, DEFAULT_DLQ_NAME_SUFFIX); + } + + /** + * Generates a dead letter exchange name for a given queue name + * + * @param name + * queue name + * @return DL exchange name + */ + protected static String getDeadLetterExchangeName(String name) + { + return name + System.getProperty(BrokerProperties.PROPERTY_DEAD_LETTER_EXCHANGE_SUFFIX, DefaultExchangeFactory.DEFAULT_DLE_NAME_SUFFIX); + } + + private static Map createQueueArgumentsFromConfig(QueueConfiguration config) + { + Map arguments = new HashMap(); + + if(config.getArguments() != null && !config.getArguments().isEmpty()) + { + arguments.putAll(QueueArgumentsConverter.convertWireArgsToModel(new HashMap(config.getArguments()))); + } + + if(config.isLVQ() || config.getLVQKey() != null) + { + arguments.put(Queue.LVQ_KEY, config.getLVQKey() == null ? QPID_DEFAULT_LVQ_KEY : config.getLVQKey()); + } + else if (config.getPriority() || config.getPriorities() > 0) + { + arguments.put(Queue.PRIORITIES, config.getPriorities() < 0 ? 10 : config.getPriorities()); + } + else if (config.getQueueSortKey() != null && !"".equals(config.getQueueSortKey())) + { + arguments.put(Queue.SORT_KEY, config.getQueueSortKey()); + } + + if (!config.getAutoDelete() && config.isDeadLetterQueueEnabled()) + { + arguments.put(Queue.CREATE_DLQ_ON_CREATION, true); + } + + if (config.getDescription() != null && !"".equals(config.getDescription())) + { + arguments.put(Queue.DESCRIPTION, config.getDescription()); + } + + if (arguments.isEmpty()) + { + return Collections.emptyMap(); + } + else + { + return arguments; + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/BaseQueue.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/BaseQueue.java new file mode 100644 index 0000000000..6145570b0c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/BaseQueue.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.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.store.TransactionLogResource; + +public interface BaseQueue extends TransactionLogResource +{ + public static interface PostEnqueueAction + { + public void onEnqueue(QueueEntry entry); + } + + void enqueue(ServerMessage message) throws AMQException; + void enqueue(ServerMessage message, PostEnqueueAction action) throws AMQException; + void enqueue(ServerMessage message, boolean transactional, PostEnqueueAction action) throws AMQException; + + boolean isDurable(); + boolean isDeleted(); + + String getName(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/ConflationQueue.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/ConflationQueue.java new file mode 100644 index 0000000000..c2813bb7a5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/ConflationQueue.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.queue; + +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ConflationQueue extends SimpleAMQQueue +{ + protected ConflationQueue(UUID id, + String name, + boolean durable, + String owner, + boolean autoDelete, + boolean exclusive, + VirtualHost virtualHost, + Map args, String conflationKey) + { + super(id, name, durable, owner, autoDelete, exclusive, virtualHost, new ConflationQueueList.Factory(conflationKey), args); + } + + public String getConflationKey() + { + return ((ConflationQueueList) getEntries()).getConflationKey(); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/ConflationQueueList.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/ConflationQueueList.java new file mode 100644 index 0000000000..53420ded9b --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/ConflationQueueList.java @@ -0,0 +1,256 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.message.ServerMessage; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; + +public class ConflationQueueList extends SimpleQueueEntryList +{ + private static final Logger LOGGER = LoggerFactory.getLogger(ConflationQueueList.class); + + private final String _conflationKey; + private final ConcurrentHashMap> _latestValuesMap = + new ConcurrentHashMap>(); + + private final QueueEntry _deleteInProgress = new SimpleQueueEntryImpl(this); + private final QueueEntry _newerEntryAlreadyBeenAndGone = new SimpleQueueEntryImpl(this); + + public ConflationQueueList(AMQQueue queue, String conflationKey) + { + super(queue); + _conflationKey = conflationKey; + } + + public String getConflationKey() + { + return _conflationKey; + } + + @Override + protected ConflationQueueEntry createQueueEntry(ServerMessage message) + { + return new ConflationQueueEntry(this, message); + } + + /** + * Updates the list using super.add and also updates {@link #_latestValuesMap} and discards entries as necessary. + */ + @Override + public ConflationQueueEntry add(final ServerMessage message) + { + final ConflationQueueEntry addedEntry = (ConflationQueueEntry) (super.add(message)); + + final Object keyValue = message.getMessageHeader().getHeader(_conflationKey); + if (keyValue != null) + { + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("Adding entry " + addedEntry + " for message " + message.getMessageNumber() + " with conflation key " + keyValue); + } + + final AtomicReference referenceToEntry = new AtomicReference(addedEntry); + AtomicReference entryReferenceFromMap = null; + QueueEntry entryFromMap; + + // Iterate until we have got a valid atomic reference object and either the referent is newer than the current + // entry, or the current entry has replaced it in the reference. Note that the _deletedEntryPlaceholder is a special value + // indicating that the reference object is no longer valid (it is being removed from the map). + boolean keepTryingToUpdateEntryReference = true; + do + { + do + { + entryReferenceFromMap = getOrPutIfAbsent(keyValue, referenceToEntry); + + // entryFromMap can be either an older entry, a newer entry (added recently by another thread), or addedEntry (if it's for a new key value) + entryFromMap = entryReferenceFromMap.get(); + } + while(entryFromMap == _deleteInProgress); + + boolean entryFromMapIsOlder = entryFromMap != _newerEntryAlreadyBeenAndGone && entryFromMap.compareTo(addedEntry) < 0; + + keepTryingToUpdateEntryReference = entryFromMapIsOlder + && !entryReferenceFromMap.compareAndSet(entryFromMap, addedEntry); + } + while(keepTryingToUpdateEntryReference); + + if (entryFromMap == _newerEntryAlreadyBeenAndGone) + { + discardEntry(addedEntry); + } + else if (entryFromMap.compareTo(addedEntry) > 0) + { + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("New entry " + addedEntry.getEntryId() + " for message " + addedEntry.getMessage().getMessageNumber() + " being immediately discarded because a newer entry arrived. The newer entry is: " + entryFromMap + " for message " + entryFromMap.getMessage().getMessageNumber()); + } + discardEntry(addedEntry); + } + else if (entryFromMap.compareTo(addedEntry) < 0) + { + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("Entry " + addedEntry + " for message " + addedEntry.getMessage().getMessageNumber() + " replacing older entry " + entryFromMap + " for message " + entryFromMap.getMessage().getMessageNumber()); + } + discardEntry(entryFromMap); + } + + addedEntry.setLatestValueReference(entryReferenceFromMap); + } + + return addedEntry; + } + + /** + * Returns: + * + *

    + *
  • the existing entry reference if the value already exists in the map, or
  • + *
  • referenceToValue if none exists, or
  • + *
  • a reference to {@link #_newerEntryAlreadyBeenAndGone} if another thread concurrently + * adds and removes during execution of this method.
  • + *
+ */ + private AtomicReference getOrPutIfAbsent(final Object key, final AtomicReference referenceToAddedValue) + { + AtomicReference latestValueReference = _latestValuesMap.putIfAbsent(key, referenceToAddedValue); + + if(latestValueReference == null) + { + latestValueReference = _latestValuesMap.get(key); + if(latestValueReference == null) + { + return new AtomicReference(_newerEntryAlreadyBeenAndGone); + } + } + return latestValueReference; + } + + private void discardEntry(final QueueEntry entry) + { + if(entry.acquire()) + { + ServerTransaction txn = new AutoCommitTransaction(getQueue().getVirtualHost().getMessageStore()); + txn.dequeue(entry.getQueue(),entry.getMessage(), + new ServerTransaction.Action() + { + @Override + public void postCommit() + { + entry.discard(); + } + + @Override + public void onRollback() + { + + } + }); + } + } + + private final class ConflationQueueEntry extends SimpleQueueEntryImpl + { + + private AtomicReference _latestValueReference; + + public ConflationQueueEntry(SimpleQueueEntryList queueEntryList, ServerMessage message) + { + super(queueEntryList, message); + } + + @Override + public void release() + { + super.release(); + + discardIfReleasedEntryIsNoLongerLatest(); + } + + @Override + public boolean delete() + { + if(super.delete()) + { + if(_latestValueReference != null && _latestValueReference.compareAndSet(this, _deleteInProgress)) + { + Object key = getMessageHeader().getHeader(_conflationKey); + _latestValuesMap.remove(key,_latestValueReference); + } + return true; + } + else + { + return false; + } + } + + public void setLatestValueReference(final AtomicReference latestValueReference) + { + _latestValueReference = latestValueReference; + } + + private void discardIfReleasedEntryIsNoLongerLatest() + { + if(_latestValueReference != null) + { + if(_latestValueReference.get() != this) + { + discardEntry(this); + } + } + } + + } + + /** + * Exposed purposes of unit test only. + */ + Map> getLatestValuesMap() + { + return Collections.unmodifiableMap(_latestValuesMap); + } + + static class Factory implements QueueEntryListFactory + { + private final String _conflationKey; + + Factory(String conflationKey) + { + _conflationKey = conflationKey; + } + + public ConflationQueueList createQueueEntryList(AMQQueue queue) + { + return new ConflationQueueList(queue, _conflationKey); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java new file mode 100644 index 0000000000..7d091dbf73 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.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.queue; + +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class DefaultQueueRegistry implements QueueRegistry +{ + private ConcurrentMap _queueMap = new ConcurrentHashMap(); + + private final VirtualHost _virtualHost; + private final Collection _listeners = + new ArrayList(); + + public DefaultQueueRegistry(VirtualHost virtualHost) + { + _virtualHost = virtualHost; + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public void registerQueue(AMQQueue queue) + { + _queueMap.put(queue.getName(), queue); + synchronized (_listeners) + { + for(RegistryChangeListener listener : _listeners) + { + listener.queueRegistered(queue); + } + } + } + + public void unregisterQueue(String name) + { + AMQQueue q = _queueMap.remove(name); + if(q != null) + { + synchronized (_listeners) + { + for(RegistryChangeListener listener : _listeners) + { + listener.queueUnregistered(q); + } + } + } + } + + + public Collection getQueues() + { + return _queueMap.values(); + } + + public AMQQueue getQueue(String queue) + { + return queue == null ? null : _queueMap.get(queue); + } + + public void addRegistryChangeListener(RegistryChangeListener listener) + { + synchronized(_listeners) + { + _listeners.add(listener); + } + } + + @Override + public void stopAllAndUnregisterMBeans() + { + for (final AMQQueue queue : getQueues()) + { + queue.stop(); + + //TODO: this is a bit of a hack, what if the listeners aren't aware + //that we are just unregistering the MBean because of HA, and aren't + //actually removing the queue as such. + synchronized (_listeners) + { + for(RegistryChangeListener listener : _listeners) + { + listener.queueUnregistered(queue); + } + } + } + _queueMap.clear(); + } + + @Override + public synchronized AMQQueue getQueue(UUID queueId) + { + Collection queues = _queueMap.values(); + for (AMQQueue queue : queues) + { + if (queue.getId().equals(queueId)) + { + return queue; + } + } + return null; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/Filterable.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/Filterable.java new file mode 100644 index 0000000000..50d8f4166d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/Filterable.java @@ -0,0 +1,32 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.message.AMQMessageHeader; + +public interface Filterable +{ + AMQMessageHeader getMessageHeader(); + + boolean isPersistent(); + + boolean isRedelivered(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/InboundMessageAdapter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/InboundMessageAdapter.java new file mode 100755 index 0000000000..2a78ee430c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/InboundMessageAdapter.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.framing.AMQShortString; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.InboundMessage; + +public class InboundMessageAdapter implements InboundMessage +{ + + private QueueEntry _entry; + + InboundMessageAdapter() + { + } + + public InboundMessageAdapter(QueueEntry entry) + { + _entry = entry; + } + + public void setEntry(QueueEntry entry) + { + _entry = entry; + } + + public String getRoutingKey() + { + return _entry.getMessage().getRoutingKey(); + } + + public AMQMessageHeader getMessageHeader() + { + return _entry.getMessageHeader(); + } + + public boolean isPersistent() + { + return _entry.isPersistent(); + } + + public boolean isRedelivered() + { + return _entry.isRedelivered(); + } + + public long getSize() + { + return _entry.getSize(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java new file mode 100644 index 0000000000..566cdd0ea5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java @@ -0,0 +1,150 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.server.message.ServerMessage; + +public enum NotificationCheck +{ + + MESSAGE_COUNT_ALERT + { + public boolean notifyIfNecessary(ServerMessage msg, AMQQueue queue, AMQQueue.NotificationListener listener) + { + int msgCount; + final long maximumMessageCount = queue.getMaximumMessageCount(); + if (maximumMessageCount!= 0 && (msgCount = queue.getMessageCount()) >= maximumMessageCount) + { + String notificationMsg = msgCount + ": Maximum count on queue threshold ("+ maximumMessageCount +") breached."; + + logNotification(this, queue, notificationMsg); + listener.notifyClients(this, queue, notificationMsg); + return true; + } + return false; + } + }, + MESSAGE_SIZE_ALERT(true) + { + public boolean notifyIfNecessary(ServerMessage msg, AMQQueue queue, AMQQueue.NotificationListener listener) + { + final long maximumMessageSize = queue.getMaximumMessageSize(); + if(maximumMessageSize != 0) + { + // Check for threshold message size + long messageSize; + messageSize = (msg == null) ? 0 : msg.getSize(); + + if (messageSize >= maximumMessageSize) + { + String notificationMsg = messageSize + "b : Maximum message size threshold ("+ maximumMessageSize +") breached. [Message ID=" + msg.getMessageNumber() + "]"; + + logNotification(this, queue, notificationMsg); + listener.notifyClients(this, queue, notificationMsg); + return true; + } + } + return false; + } + + }, + QUEUE_DEPTH_ALERT + { + public boolean notifyIfNecessary(ServerMessage msg, AMQQueue queue, AMQQueue.NotificationListener listener) + { + // Check for threshold queue depth in bytes + final long maximumQueueDepth = queue.getMaximumQueueDepth(); + + if(maximumQueueDepth != 0) + { + final long queueDepth = queue.getQueueDepth(); + + if (queueDepth >= maximumQueueDepth) + { + String notificationMsg = (queueDepth>>10) + "Kb : Maximum queue depth threshold ("+(maximumQueueDepth>>10)+"Kb) breached."; + + logNotification(this, queue, notificationMsg); + listener.notifyClients(this, queue, notificationMsg); + return true; + } + } + return false; + } + + }, + MESSAGE_AGE_ALERT + { + public boolean notifyIfNecessary(ServerMessage msg, AMQQueue queue, AMQQueue.NotificationListener 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; + String notificationMsg = (oldestAge/1000) + "s : Maximum age on queue threshold ("+(maxMessageAge /1000)+"s) breached."; + + logNotification(this, queue, notificationMsg); + listener.notifyClients(this, queue, notificationMsg); + + return true; + } + } + return false; + + } + + } + ; + + private static final Logger LOGGER = Logger.getLogger(NotificationCheck.class); + + private final boolean _messageSpecific; + + NotificationCheck() + { + this(false); + } + + NotificationCheck(boolean messageSpecific) + { + _messageSpecific = messageSpecific; + } + + public boolean isMessageSpecific() + { + return _messageSpecific; + } + + public abstract boolean notifyIfNecessary(ServerMessage msg, AMQQueue queue, AMQQueue.NotificationListener listener); + + //A bit of a hack, only for use until we do the logging listener + private static void logNotification(NotificationCheck notification, AMQQueue queue, String notificationMsg) + { + LOGGER.info(notification.name() + " On Queue " + queue.getName() + " - " + notificationMsg); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/OutOfOrderQueue.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/OutOfOrderQueue.java new file mode 100644 index 0000000000..daa5db393a --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/OutOfOrderQueue.java @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionList; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Map; +import java.util.UUID; + +public abstract class OutOfOrderQueue extends SimpleAMQQueue +{ + + protected OutOfOrderQueue(UUID id, String name, boolean durable, + String owner, boolean autoDelete, boolean exclusive, + VirtualHost virtualHost, QueueEntryListFactory entryListFactory, Map arguments) + { + super(id, name, durable, owner, autoDelete, exclusive, virtualHost, entryListFactory, arguments); + } + + @Override + protected void checkSubscriptionsNotAheadOfDelivery(final QueueEntry entry) + { + // check that all subscriptions are not in advance of the entry + SubscriptionList.SubscriptionNodeIterator subIter = getSubscriptionList().iterator(); + while(subIter.advance() && !entry.isAcquired()) + { + final Subscription subscription = subIter.getNode().getSubscription(); + if(!subscription.isClosed()) + { + QueueContext context = (QueueContext) subscription.getQueueContext(); + if(context != null) + { + QueueEntry released = context.getReleasedEntry(); + while(!entry.isAcquired() && (released == null || released.compareTo(entry) > 0)) + { + if(QueueContext._releasedUpdater.compareAndSet(context,released,entry)) + { + break; + } + else + { + released = context.getReleasedEntry(); + } + } + } + } + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java new file mode 100644 index 0000000000..66315af9fb --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.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.queue; + +import org.apache.qpid.server.message.ServerMessage; + +public class PriorityQueueList implements QueueEntryList +{ + private final AMQQueue _queue; + private final PriorityQueueEntrySubList[] _priorityLists; + private final int _priorities; + private final int _priorityOffset; + + public PriorityQueueList(AMQQueue queue, int priorities) + { + _queue = queue; + _priorityLists = new PriorityQueueEntrySubList[priorities]; + _priorities = priorities; + _priorityOffset = 5-((priorities + 1)/2); + for(int i = 0; i < priorities; i++) + { + _priorityLists[i] = new PriorityQueueEntrySubList(queue, i); + } + } + + public int getPriorities() + { + return _priorities; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public SimpleQueueEntryImpl add(ServerMessage message) + { + int index = message.getMessageHeader().getPriority() - _priorityOffset; + if(index >= _priorities) + { + index = _priorities-1; + } + else if(index < 0) + { + index = 0; + } + return _priorityLists[index].add(message); + + } + + public SimpleQueueEntryImpl next(SimpleQueueEntryImpl node) + { + SimpleQueueEntryImpl next = node.getNextValidEntry(); + + if(next == null) + { + final QueueEntryList nodeEntryList = node.getQueueEntryList(); + int index; + for(index = _priorityLists.length-1; _priorityLists[index] != nodeEntryList; index--) {}; + + while(next == null && index != 0) + { + index--; + next = _priorityLists[index].getHead().getNextValidEntry(); + } + + } + return next; + } + + private final class PriorityQueueEntryListIterator implements QueueEntryIterator + { + private final SimpleQueueEntryList.QueueEntryIteratorImpl[] _iterators = new SimpleQueueEntryList.QueueEntryIteratorImpl[ _priorityLists.length ]; + private SimpleQueueEntryImpl _lastNode; + + PriorityQueueEntryListIterator() + { + for(int i = 0; i < _priorityLists.length; i++) + { + _iterators[i] = _priorityLists[i].iterator(); + } + _lastNode = _iterators[_iterators.length - 1].getNode(); + } + + + public boolean atTail() + { + for(int i = 0; i < _iterators.length; i++) + { + if(!_iterators[i].atTail()) + { + return false; + } + } + return true; + } + + public SimpleQueueEntryImpl getNode() + { + return _lastNode; + } + + public boolean advance() + { + for(int i = _iterators.length-1; i >= 0; i--) + { + if(_iterators[i].advance()) + { + _lastNode = _iterators[i].getNode(); + return true; + } + } + return false; + } + } + + public PriorityQueueEntryListIterator iterator() + { + return new PriorityQueueEntryListIterator(); + } + + public SimpleQueueEntryImpl getHead() + { + return _priorityLists[_priorities-1].getHead(); + } + + public void entryDeleted(final SimpleQueueEntryImpl queueEntry) + { + + } + + static class Factory implements QueueEntryListFactory + { + private final int _priorities; + + Factory(int priorities) + { + _priorities = priorities; + } + + public PriorityQueueList createQueueEntryList(AMQQueue queue) + { + return new PriorityQueueList(queue, _priorities); + } + } + + private static class PriorityQueueEntrySubList extends SimpleQueueEntryList + { + private int _listPriority; + + public PriorityQueueEntrySubList(AMQQueue queue, int listPriority) + { + super(queue); + _listPriority = listPriority; + } + + @Override + protected PriorityQueueEntryImpl createQueueEntry(ServerMessage message) + { + return new PriorityQueueEntryImpl(this, message); + } + + public int getListPriority() + { + return _listPriority; + } + } + + private static class PriorityQueueEntryImpl extends SimpleQueueEntryImpl + { + public PriorityQueueEntryImpl(PriorityQueueEntrySubList queueEntryList, ServerMessage message) + { + super(queueEntryList, message); + } + + @Override + public int compareTo(final QueueEntry o) + { + PriorityQueueEntrySubList pqel = (PriorityQueueEntrySubList)((PriorityQueueEntryImpl)o).getQueueEntryList(); + int otherPriority = pqel.getListPriority(); + int thisPriority = ((PriorityQueueEntrySubList) getQueueEntryList()).getListPriority(); + + if(thisPriority != otherPriority) + { + /* + * Different priorities, so answer can only be greater than or less than + * + * A message with higher priority (e.g. 5) is conceptually 'earlier' in the + * priority queue than one with a lower priority (e.g. 4). + */ + return thisPriority > otherPriority ? -1 : 1; + } + else + { + return super.compareTo(o); + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueArgumentsConverter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueArgumentsConverter.java new file mode 100644 index 0000000000..589f385d22 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueArgumentsConverter.java @@ -0,0 +1,152 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.qpid.server.model.Queue; + +public class QueueArgumentsConverter +{ + public static final String X_QPID_FLOW_RESUME_CAPACITY = "x-qpid-flow-resume-capacity"; + public static final String X_QPID_CAPACITY = "x-qpid-capacity"; + public static final String X_QPID_MINIMUM_ALERT_REPEAT_GAP = "x-qpid-minimum-alert-repeat-gap"; + public static final String X_QPID_MAXIMUM_MESSAGE_COUNT = "x-qpid-maximum-message-count"; + public static final String X_QPID_MAXIMUM_MESSAGE_SIZE = "x-qpid-maximum-message-size"; + public static final String X_QPID_MAXIMUM_MESSAGE_AGE = "x-qpid-maximum-message-age"; + public static final String X_QPID_MAXIMUM_QUEUE_DEPTH = "x-qpid-maximum-queue-depth"; + + public static final String QPID_ALERT_COUNT = "qpid.alert_count"; + public static final String QPID_ALERT_SIZE = "qpid.alert_size"; + public static final String QPID_ALERT_REPEAT_GAP = "qpid.alert_repeat_gap"; + + public static final String X_QPID_PRIORITIES = "x-qpid-priorities"; + + public static final String X_QPID_DESCRIPTION = "x-qpid-description"; + + public static final String QPID_LAST_VALUE_QUEUE_KEY = "qpid.last_value_queue_key"; + + public static final String QPID_QUEUE_SORT_KEY = "qpid.queue_sort_key"; + public static final String X_QPID_DLQ_ENABLED = "x-qpid-dlq-enabled"; + public static final String X_QPID_MAXIMUM_DELIVERY_COUNT = "x-qpid-maximum-delivery-count"; + public static final String QPID_GROUP_HEADER_KEY = "qpid.group_header_key"; + public static final String QPID_SHARED_MSG_GROUP = "qpid.shared_msg_group"; + public static final String QPID_DEFAULT_MESSAGE_GROUP_ARG = "qpid.default-message-group"; + public static final String QPID_TRACE_EXCLUDE = "qpid.trace.exclude"; + public static final String QPID_TRACE_ID = "qpid.trace.id"; + + public static final String QPID_LAST_VALUE_QUEUE = "qpid.last_value_queue"; + + /** + * No-local queue argument is used to support the no-local feature of Durable Subscribers. + */ + public static final String QPID_NO_LOCAL = "no-local"; + static final Map ATTRIBUTE_MAPPINGS = new LinkedHashMap(); + static + { + ATTRIBUTE_MAPPINGS.put(X_QPID_MINIMUM_ALERT_REPEAT_GAP, Queue.ALERT_REPEAT_GAP); + ATTRIBUTE_MAPPINGS.put(X_QPID_MAXIMUM_MESSAGE_AGE, Queue.ALERT_THRESHOLD_MESSAGE_AGE); + ATTRIBUTE_MAPPINGS.put(X_QPID_MAXIMUM_MESSAGE_SIZE, Queue.ALERT_THRESHOLD_MESSAGE_SIZE); + + ATTRIBUTE_MAPPINGS.put(X_QPID_MAXIMUM_MESSAGE_COUNT, Queue.ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES); + ATTRIBUTE_MAPPINGS.put(X_QPID_MAXIMUM_QUEUE_DEPTH, Queue.ALERT_THRESHOLD_QUEUE_DEPTH_BYTES); + ATTRIBUTE_MAPPINGS.put(QPID_ALERT_COUNT, Queue.ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES); + ATTRIBUTE_MAPPINGS.put(QPID_ALERT_SIZE, Queue.ALERT_THRESHOLD_QUEUE_DEPTH_BYTES); + ATTRIBUTE_MAPPINGS.put(QPID_ALERT_REPEAT_GAP, Queue.ALERT_REPEAT_GAP); + + ATTRIBUTE_MAPPINGS.put(X_QPID_MAXIMUM_DELIVERY_COUNT, Queue.MAXIMUM_DELIVERY_ATTEMPTS); + + ATTRIBUTE_MAPPINGS.put(X_QPID_CAPACITY, Queue.QUEUE_FLOW_CONTROL_SIZE_BYTES); + ATTRIBUTE_MAPPINGS.put(X_QPID_FLOW_RESUME_CAPACITY, Queue.QUEUE_FLOW_RESUME_SIZE_BYTES); + + ATTRIBUTE_MAPPINGS.put(QPID_QUEUE_SORT_KEY, Queue.SORT_KEY); + ATTRIBUTE_MAPPINGS.put(QPID_LAST_VALUE_QUEUE_KEY, Queue.LVQ_KEY); + ATTRIBUTE_MAPPINGS.put(X_QPID_PRIORITIES, Queue.PRIORITIES); + + ATTRIBUTE_MAPPINGS.put(X_QPID_DESCRIPTION, Queue.DESCRIPTION); + + ATTRIBUTE_MAPPINGS.put(X_QPID_DLQ_ENABLED, Queue.CREATE_DLQ_ON_CREATION); + ATTRIBUTE_MAPPINGS.put(QPID_GROUP_HEADER_KEY, Queue.MESSAGE_GROUP_KEY); + //ATTRIBUTE_MAPPINGS.put(QPID_SHARED_MSG_GROUP, Queue.MESSAGE_GROUP_SHARED_GROUPS); + ATTRIBUTE_MAPPINGS.put(QPID_DEFAULT_MESSAGE_GROUP_ARG, Queue.MESSAGE_GROUP_DEFAULT_GROUP); + ATTRIBUTE_MAPPINGS.put(QPID_TRACE_EXCLUDE, Queue.FEDERATION_EXCLUDES); + ATTRIBUTE_MAPPINGS.put(QPID_TRACE_ID, Queue.FEDERATION_ID); + ATTRIBUTE_MAPPINGS.put(QPID_NO_LOCAL, Queue.NO_LOCAL); + + } + + + public static Map convertWireArgsToModel(Map wireArguments) + { + Map modelArguments = new HashMap(); + if(wireArguments != null) + { + for(Map.Entry entry : ATTRIBUTE_MAPPINGS.entrySet()) + { + if(wireArguments.containsKey(entry.getKey())) + { + modelArguments.put(entry.getValue(), wireArguments.get(entry.getKey())); + } + } + if(wireArguments.containsKey(QPID_LAST_VALUE_QUEUE) && !wireArguments.containsKey(QPID_LAST_VALUE_QUEUE_KEY)) + { + modelArguments.put(Queue.LVQ_KEY, AMQQueueFactory.QPID_DEFAULT_LVQ_KEY); + } + if(wireArguments.containsKey(QPID_SHARED_MSG_GROUP)) + { + modelArguments.put(Queue.MESSAGE_GROUP_SHARED_GROUPS, + SimpleAMQQueue.SHARED_MSG_GROUP_ARG_VALUE.equals(String.valueOf(wireArguments.get(QPID_SHARED_MSG_GROUP)))); + } + if(wireArguments.get(X_QPID_DLQ_ENABLED) != null) + { + modelArguments.put(Queue.CREATE_DLQ_ON_CREATION, Boolean.parseBoolean(wireArguments.get(X_QPID_DLQ_ENABLED).toString())); + } + + if(wireArguments.get(QPID_NO_LOCAL) != null) + { + modelArguments.put(Queue.NO_LOCAL, Boolean.parseBoolean(wireArguments.get(QPID_NO_LOCAL).toString())); + } + + } + return modelArguments; + } + + + public static Map convertModelArgsToWire(Map modelArguments) + { + Map wireArguments = new HashMap(); + for(Map.Entry entry : ATTRIBUTE_MAPPINGS.entrySet()) + { + if(modelArguments.containsKey(entry.getValue())) + { + wireArguments.put(entry.getKey(), modelArguments.get(entry.getValue())); + } + } + + if(Boolean.TRUE.equals(modelArguments.get(Queue.MESSAGE_GROUP_SHARED_GROUPS))) + { + wireArguments.put(QPID_SHARED_MSG_GROUP, SimpleAMQQueue.SHARED_MSG_GROUP_ARG_VALUE); + } + + return wireArguments; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueContext.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueContext.java new file mode 100755 index 0000000000..79279b44c7 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueContext.java @@ -0,0 +1,64 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.queue; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +final class QueueContext implements AMQQueue.Context +{ + private volatile QueueEntry _lastSeenEntry; + private volatile QueueEntry _releasedEntry; + + static final AtomicReferenceFieldUpdater + _lastSeenUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueContext.class, QueueEntry.class, "_lastSeenEntry"); + static final AtomicReferenceFieldUpdater + _releasedUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueContext.class, QueueEntry.class, "_releasedEntry"); + + public QueueContext(QueueEntry head) + { + _lastSeenEntry = head; + } + + public QueueEntry getLastSeenEntry() + { + return _lastSeenEntry; + } + + + QueueEntry getReleasedEntry() + { + return _releasedEntry; + } + + @Override + public String toString() + { + return "QueueContext{" + + "_lastSeenEntry=" + _lastSeenEntry + + ", _releasedEntry=" + _releasedEntry + + '}'; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntry.java new file mode 100644 index 0000000000..c44961c457 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntry.java @@ -0,0 +1,253 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* 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.message.ServerMessage; +import org.apache.qpid.server.subscription.Subscription; + +public interface QueueEntry extends Comparable, Filterable +{ + + + + public static enum State + { + AVAILABLE, + ACQUIRED, + EXPIRED, + DEQUEUED, + DELETED; + + + } + + public static interface StateChangeListener + { + public void stateChanged(QueueEntry entry, State oldSate, State newState); + } + + public abstract class EntryState + { + private EntryState() + { + } + + public abstract State getState(); + + /** + * Returns true if state is either DEQUEUED or DELETED. + * + * @return true if state is either DEQUEUED or DELETED. + */ + public boolean isDispensed() + { + State currentState = getState(); + return currentState == State.DEQUEUED || currentState == State.DELETED; + } + } + + + public final class AvailableState extends EntryState + { + + public State getState() + { + return State.AVAILABLE; + } + + public String toString() + { + return getState().name(); + } + } + + + public final class DequeuedState extends EntryState + { + + public State getState() + { + return State.DEQUEUED; + } + + public String toString() + { + return getState().name(); + } + } + + + public final class DeletedState extends EntryState + { + + public State getState() + { + return State.DELETED; + } + + public String toString() + { + return getState().name(); + } + } + + public final class ExpiredState extends EntryState + { + + public State getState() + { + return State.EXPIRED; + } + + public String toString() + { + return getState().name(); + } + } + + + public final class NonSubscriptionAcquiredState extends EntryState + { + public State getState() + { + return State.ACQUIRED; + } + + public String toString() + { + return getState().name(); + } + } + + public final class SubscriptionAcquiredState extends EntryState + { + private final Subscription _subscription; + + public SubscriptionAcquiredState(Subscription subscription) + { + _subscription = subscription; + } + + + public State getState() + { + return State.ACQUIRED; + } + + public Subscription getSubscription() + { + return _subscription; + } + + public String toString() + { + return "{" + getState().name() + " : " + _subscription +"}"; + } + } + + + final static EntryState AVAILABLE_STATE = new AvailableState(); + final static EntryState DELETED_STATE = new DeletedState(); + final static EntryState DEQUEUED_STATE = new DequeuedState(); + final static EntryState NON_SUBSCRIPTION_ACQUIRED_STATE = new NonSubscriptionAcquiredState(); + + + + + AMQQueue getQueue(); + + ServerMessage getMessage(); + + long getSize(); + + boolean getDeliveredToConsumer(); + + boolean expired() throws AMQException; + + boolean isAvailable(); + + boolean isAcquired(); + + boolean acquire(); + boolean acquire(Subscription sub); + + boolean delete(); + boolean isDeleted(); + + boolean acquiredBySubscription(); + boolean isAcquiredBy(Subscription subscription); + + void release(); + + void setRedelivered(); + + boolean isRedelivered(); + + Subscription getDeliveredSubscription(); + + void reject(); + + boolean isRejectedBy(long subscriptionId); + + void dequeue(); + + void dispose(); + + void discard(); + + void routeToAlternate(); + + boolean isQueueDeleted(); + + QueueEntry getNextNode(); + + QueueEntry getNextValidEntry(); + + void addStateChangeListener(StateChangeListener listener); + boolean removeStateChangeListener(StateChangeListener listener); + + /** + * Returns true if entry is in DEQUEUED state, otherwise returns false. + * + * @return true if entry is in DEQUEUED state, otherwise returns false + */ + boolean isDequeued(); + + /** + * Returns true if entry is either DEQUED or DELETED state. + * + * @return true if entry is either DEQUED or DELETED state + */ + boolean isDispensed(); + + /** + * Number of times this queue entry has been delivered. + * + * @return delivery count + */ + int getDeliveryCount(); + + void incrementDeliveryCount(); + + void decrementDeliveryCount(); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java new file mode 100644 index 0000000000..36feb27d86 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java @@ -0,0 +1,517 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.txn.LocalTransaction; +import org.apache.qpid.server.txn.ServerTransaction; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +public abstract class QueueEntryImpl implements QueueEntry +{ + private static final Logger _log = Logger.getLogger(QueueEntryImpl.class); + + private final QueueEntryList _queueEntryList; + + private MessageReference _message; + + private Set _rejectedBy = null; + + private volatile EntryState _state = AVAILABLE_STATE; + + private static final + AtomicReferenceFieldUpdater + _stateUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueEntryImpl.class, EntryState.class, "_state"); + + + private volatile Set _stateChangeListeners; + + private static final + AtomicReferenceFieldUpdater + _listenersUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueEntryImpl.class, Set.class, "_stateChangeListeners"); + + + private static final + AtomicLongFieldUpdater + _entryIdUpdater = + AtomicLongFieldUpdater.newUpdater + (QueueEntryImpl.class, "_entryId"); + + + private volatile long _entryId; + + private static final int DELIVERED_TO_CONSUMER = 1; + private static final int REDELIVERED = 2; + + private volatile int _deliveryState; + + /** Number of times this message has been delivered */ + private volatile int _deliveryCount = 0; + private static final AtomicIntegerFieldUpdater _deliveryCountUpdater = AtomicIntegerFieldUpdater + .newUpdater(QueueEntryImpl.class, "_deliveryCount"); + + + + public QueueEntryImpl(QueueEntryList queueEntryList) + { + this(queueEntryList,null,Long.MIN_VALUE); + _state = DELETED_STATE; + } + + + public QueueEntryImpl(QueueEntryList queueEntryList, ServerMessage message, final long entryId) + { + _queueEntryList = queueEntryList; + + _message = message == null ? null : message.newReference(); + + _entryIdUpdater.set(this, entryId); + } + + public QueueEntryImpl(QueueEntryList queueEntryList, ServerMessage message) + { + _queueEntryList = queueEntryList; + _message = message == null ? null : message.newReference(); + } + + protected void setEntryId(long entryId) + { + _entryIdUpdater.set(this, entryId); + } + + protected long getEntryId() + { + return _entryId; + } + + public AMQQueue getQueue() + { + return _queueEntryList.getQueue(); + } + + public ServerMessage getMessage() + { + return _message == null ? null : _message.getMessage(); + } + + public long getSize() + { + return getMessage() == null ? 0 : getMessage().getSize(); + } + + public boolean getDeliveredToConsumer() + { + return (_deliveryState & DELIVERED_TO_CONSUMER) != 0; + } + + public boolean expired() throws AMQException + { + ServerMessage message = getMessage(); + if(message != null) + { + long expiration = message.getExpiration(); + if (expiration != 0L) + { + long now = System.currentTimeMillis(); + + return (now > expiration); + } + } + return false; + + } + + public boolean isAvailable() + { + return _state == AVAILABLE_STATE; + } + + public boolean isAcquired() + { + return _state.getState() == State.ACQUIRED; + } + + public boolean acquire() + { + return acquire(NON_SUBSCRIPTION_ACQUIRED_STATE); + } + + private boolean acquire(final EntryState state) + { + boolean acquired = _stateUpdater.compareAndSet(this,AVAILABLE_STATE, state); + + if(acquired && _stateChangeListeners != null) + { + notifyStateChange(State.AVAILABLE, State.ACQUIRED); + } + + return acquired; + } + + public boolean acquire(Subscription sub) + { + final boolean acquired = acquire(sub.getOwningState()); + if(acquired) + { + _deliveryState |= DELIVERED_TO_CONSUMER; + } + return acquired; + } + + public boolean acquiredBySubscription() + { + + return (_state instanceof SubscriptionAcquiredState); + } + + public boolean isAcquiredBy(Subscription subscription) + { + EntryState state = _state; + return state instanceof SubscriptionAcquiredState + && ((SubscriptionAcquiredState)state).getSubscription() == subscription; + } + + public void release() + { + EntryState state = _state; + + if((state.getState() == State.ACQUIRED) &&_stateUpdater.compareAndSet(this, state, AVAILABLE_STATE)) + { + + if(state instanceof SubscriptionAcquiredState) + { + getQueue().decrementUnackedMsgCount(this); + Subscription subscription = ((SubscriptionAcquiredState)state).getSubscription(); + if (subscription != null) + { + subscription.releaseQueueEntry(this); + } + } + + if(!getQueue().isDeleted()) + { + getQueue().requeue(this); + if(_stateChangeListeners != null) + { + notifyStateChange(QueueEntry.State.ACQUIRED, QueueEntry.State.AVAILABLE); + } + + } + else if(acquire()) + { + routeToAlternate(); + } + } + + } + + public void setRedelivered() + { + _deliveryState |= REDELIVERED; + } + + public AMQMessageHeader getMessageHeader() + { + final ServerMessage message = getMessage(); + return message == null ? null : message.getMessageHeader(); + } + + public boolean isPersistent() + { + final ServerMessage message = getMessage(); + return message != null && message.isPersistent(); + } + + public boolean isRedelivered() + { + return (_deliveryState & REDELIVERED) != 0; + } + + public Subscription getDeliveredSubscription() + { + EntryState state = _state; + if (state instanceof SubscriptionAcquiredState) + { + return ((SubscriptionAcquiredState) state).getSubscription(); + } + else + { + return null; + } + } + + public void reject() + { + Subscription subscription = getDeliveredSubscription(); + + if (subscription != null) + { + if (_rejectedBy == null) + { + _rejectedBy = new HashSet(); + } + + _rejectedBy.add(subscription.getSubscriptionID()); + } + else + { + _log.warn("Requesting rejection by null subscriber:" + this); + } + } + + public boolean isRejectedBy(long subscriptionId) + { + + if (_rejectedBy != null) // We have subscriptions that rejected this message + { + return _rejectedBy.contains(subscriptionId); + } + else // This messasge hasn't been rejected yet. + { + return false; + } + } + + public void dequeue() + { + EntryState state = _state; + + if((state.getState() == State.ACQUIRED) &&_stateUpdater.compareAndSet(this, state, DEQUEUED_STATE)) + { + Subscription s = null; + if (state instanceof SubscriptionAcquiredState) + { + getQueue().decrementUnackedMsgCount(this); + s = ((SubscriptionAcquiredState) state).getSubscription(); + s.onDequeue(this); + } + + getQueue().dequeue(this,s); + if(_stateChangeListeners != null) + { + notifyStateChange(state.getState() , QueueEntry.State.DEQUEUED); + } + + } + + } + + private void notifyStateChange(final State oldState, final State newState) + { + for(StateChangeListener l : _stateChangeListeners) + { + l.stateChanged(this, oldState, newState); + } + } + + public void dispose() + { + if(delete()) + { + _message.release(); + } + } + + public void discard() + { + //if the queue is null then the message is waiting to be acked, but has been removed. + if (getQueue() != null) + { + dequeue(); + } + + dispose(); + } + + public void routeToAlternate() + { + final AMQQueue currentQueue = getQueue(); + Exchange alternateExchange = currentQueue.getAlternateExchange(); + + if (alternateExchange != null) + { + InboundMessageAdapter inboundMessageAdapter = new InboundMessageAdapter(this); + List queues = alternateExchange.route(inboundMessageAdapter); + final ServerMessage message = getMessage(); + if ((queues == null || queues.size() == 0) && alternateExchange.getAlternateExchange() != null) + { + queues = alternateExchange.getAlternateExchange().route(inboundMessageAdapter); + } + + + + if (queues != null && queues.size() != 0) + { + final List rerouteQueues = queues; + ServerTransaction txn = new LocalTransaction(getQueue().getVirtualHost().getMessageStore()); + + txn.enqueue(rerouteQueues, message, new ServerTransaction.Action() + { + public void postCommit() + { + try + { + for (BaseQueue queue : rerouteQueues) + { + queue.enqueue(message); + } + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + } + + public void onRollback() + { + + } + }); + + txn.dequeue(currentQueue, message, new ServerTransaction.Action() + { + public void postCommit() + { + discard(); + } + + public void onRollback() + { + + } + }); + + txn.commit(); + } + } + } + + public boolean isQueueDeleted() + { + return getQueue().isDeleted(); + } + + public void addStateChangeListener(StateChangeListener listener) + { + Set listeners = _stateChangeListeners; + if(listeners == null) + { + _listenersUpdater.compareAndSet(this, null, new CopyOnWriteArraySet()); + listeners = _stateChangeListeners; + } + + listeners.add(listener); + } + + public boolean removeStateChangeListener(StateChangeListener listener) + { + Set listeners = _stateChangeListeners; + if(listeners != null) + { + return listeners.remove(listener); + } + + return false; + } + + + public int compareTo(final QueueEntry o) + { + QueueEntryImpl other = (QueueEntryImpl)o; + return getEntryId() > other.getEntryId() ? 1 : getEntryId() < other.getEntryId() ? -1 : 0; + } + + public boolean isDeleted() + { + return _state == DELETED_STATE; + } + + public boolean delete() + { + EntryState state = _state; + + if(state != DELETED_STATE && _stateUpdater.compareAndSet(this,state,DELETED_STATE)) + { + _queueEntryList.entryDeleted(this); + return true; + } + else + { + return false; + } + } + + public QueueEntryList getQueueEntryList() + { + return _queueEntryList; + } + + public boolean isDequeued() + { + return _state == DEQUEUED_STATE; + } + + public boolean isDispensed() + { + return _state.isDispensed(); + } + + public int getDeliveryCount() + { + return _deliveryCount; + } + + public void incrementDeliveryCount() + { + _deliveryCountUpdater.incrementAndGet(this); + } + + public void decrementDeliveryCount() + { + _deliveryCountUpdater.decrementAndGet(this); + } + + public String toString() + { + return "QueueEntryImpl{" + + "_entryId=" + _entryId + + ", _state=" + _state + + '}'; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java new file mode 100644 index 0000000000..73ebb0f300 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java @@ -0,0 +1,30 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.queue; + +public interface QueueEntryIterator +{ + boolean atTail(); + + QE getNode(); + + boolean advance(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java new file mode 100644 index 0000000000..641aaa0a08 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java @@ -0,0 +1,40 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.message.ServerMessage; + +public interface QueueEntryList +{ + AMQQueue getQueue(); + + Q add(ServerMessage message); + + Q next(Q node); + + QueueEntryIterator iterator(); + + Q getHead(); + + void entryDeleted(Q queueEntry); + + int getPriorities(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java new file mode 100644 index 0000000000..4dbce45f67 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java @@ -0,0 +1,26 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.queue; + +interface QueueEntryListFactory +{ + public QueueEntryList createQueueEntryList(AMQQueue queue); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryVisitor.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryVisitor.java new file mode 100644 index 0000000000..9ecaf6dafd --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueEntryVisitor.java @@ -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. +*/ +package org.apache.qpid.server.queue; + +public interface QueueEntryVisitor +{ + boolean visit(QueueEntry entry); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueFactory.java new file mode 100644 index 0000000000..3e4e1df5a2 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueFactory.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 java.util.Map; +import java.util.UUID; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; + +public interface QueueFactory +{ + AMQQueue createQueue(UUID id, + String queueName, + boolean durable, + String owner, + boolean autoDelete, + boolean exclusive, + boolean deleteOnNoConsumer, + Map arguments) throws AMQSecurityException, AMQException; + + AMQQueue restoreQueue(UUID id, + String queueName, + String owner, + boolean autoDelete, + boolean exclusive, + boolean deleteOnNoConsumer, + Map arguments) throws AMQSecurityException, AMQException; + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java new file mode 100644 index 0000000000..bc1d5942bd --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueRegistry.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.queue; + +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Collection; +import java.util.UUID; + +public interface QueueRegistry +{ + VirtualHost getVirtualHost(); + + void registerQueue(AMQQueue queue); + + void unregisterQueue(String name); + + Collection getQueues(); + + AMQQueue getQueue(String queue); + + void addRegistryChangeListener(RegistryChangeListener listener); + + void stopAllAndUnregisterMBeans(); + + AMQQueue getQueue(UUID queueId); + + interface RegistryChangeListener + { + void queueRegistered(AMQQueue queue); + void queueUnregistered(AMQQueue queue); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueRunner.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueRunner.java new file mode 100644 index 0000000000..22a2029494 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/QueueRunner.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.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.transport.TransportException; + +/** + * QueueRunners are Runnables used to process a queue when requiring + * asynchronous message delivery to subscriptions, which is necessary + * when straight-through delivery of a message to a subscription isn't + * possible during the enqueue operation. + */ +public class QueueRunner implements Runnable +{ + private static final Logger _logger = Logger.getLogger(QueueRunner.class); + + private final SimpleAMQQueue _queue; + + private static int IDLE = 0; + private static int SCHEDULED = 1; + private static int RUNNING = 2; + + private final AtomicInteger _scheduled = new AtomicInteger(IDLE); + + private final AtomicBoolean _stateChange = new AtomicBoolean(); + + private final AtomicLong _lastRunAgain = new AtomicLong(); + private final AtomicLong _lastRunTime = new AtomicLong(); + + public QueueRunner(SimpleAMQQueue queue) + { + _queue = queue; + } + + public void run() + { + if(_scheduled.compareAndSet(SCHEDULED,RUNNING)) + { + long runAgain = Long.MIN_VALUE; + _stateChange.set(false); + try + { + CurrentActor.set(_queue.getLogActor()); + + runAgain = _queue.processQueue(this); + } + catch (final AMQException e) + { + _logger.error("Exception during asynchronous delivery by " + toString(), e); + } + catch (final TransportException transe) + { + final String errorMessage = "Problem during asynchronous delivery by " + toString(); + if(_logger.isDebugEnabled()) + { + _logger.debug(errorMessage, transe); + } + else + { + _logger.info(errorMessage + ' ' + transe.getMessage()); + } + } + finally + { + CurrentActor.remove(); + _scheduled.compareAndSet(RUNNING, IDLE); + final long stateChangeCount = _queue.getStateChangeCount(); + _lastRunAgain.set(runAgain); + _lastRunTime.set(System.nanoTime()); + if(runAgain == 0L || runAgain != stateChangeCount || _stateChange.compareAndSet(true,false)) + { + if(_scheduled.compareAndSet(IDLE, SCHEDULED)) + { + _queue.execute(this); + } + } + } + + } + } + + public String toString() + { + return "QueueRunner-" + _queue.getLogActor(); + } + + public void execute(Executor executor) + { + _stateChange.set(true); + if(_scheduled.compareAndSet(IDLE, SCHEDULED)) + { + executor.execute(this); + } + } + + public boolean isIdle() + { + return _scheduled.get() == IDLE; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java new file mode 100644 index 0000000000..fb36433799 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java @@ -0,0 +1,2289 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.queue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.pool.ReferenceCountingExecutorService; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.QueueActor; +import org.apache.qpid.server.logging.messages.QueueMessages; +import org.apache.qpid.server.logging.subjects.QueueLogSubject; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.security.AuthorizationHolder; +import org.apache.qpid.server.subscription.AssignedSubscriptionMessageGroupManager; +import org.apache.qpid.server.subscription.DefinedGroupMessageGroupManager; +import org.apache.qpid.server.subscription.MessageGroupManager; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionList; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.LocalTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class SimpleAMQQueue implements AMQQueue, Subscription.StateListener, MessageGroupManager.SubscriptionResetHelper +{ + + private static final Logger _logger = Logger.getLogger(SimpleAMQQueue.class); + + public static final String SHARED_MSG_GROUP_ARG_VALUE = "1"; + private static final String QPID_NO_GROUP = "qpid.no-group"; + private static final String DEFAULT_SHARED_MESSAGE_GROUP = System.getProperty(BrokerProperties.PROPERTY_DEFAULT_SHARED_MESSAGE_GROUP, QPID_NO_GROUP); + + // TODO - should make this configurable at the vhost / broker level + private static final int DEFAULT_MAX_GROUPS = 255; + + private final VirtualHost _virtualHost; + + private final String _name; + + /** null means shared */ + private final String _owner; + + private AuthorizationHolder _authorizationHolder; + + private boolean _exclusive = false; + private AMQSessionModel _exclusiveOwner; + + + private final boolean _durable; + + /** If true, this queue is deleted when the last subscriber is removed */ + private final boolean _autoDelete; + + private Exchange _alternateExchange; + + + private final QueueEntryList _entries; + + private final SubscriptionList _subscriptionList = new SubscriptionList(); + + private volatile Subscription _exclusiveSubscriber; + + + + private final AtomicInteger _atomicQueueCount = new AtomicInteger(0); + + private final AtomicLong _atomicQueueSize = new AtomicLong(0L); + + private final AtomicInteger _activeSubscriberCount = new AtomicInteger(); + + private final AtomicLong _totalMessagesReceived = new AtomicLong(); + + private final AtomicLong _dequeueCount = new AtomicLong(); + private final AtomicLong _dequeueSize = new AtomicLong(); + private final AtomicLong _enqueueCount = new AtomicLong(); + private final AtomicLong _enqueueSize = new AtomicLong(); + private final AtomicLong _persistentMessageEnqueueSize = new AtomicLong(); + private final AtomicLong _persistentMessageDequeueSize = new AtomicLong(); + private final AtomicLong _persistentMessageEnqueueCount = new AtomicLong(); + private final AtomicLong _persistentMessageDequeueCount = new AtomicLong(); + private final AtomicInteger _counsumerCountHigh = new AtomicInteger(0); + private final AtomicLong _msgTxnEnqueues = new AtomicLong(0); + private final AtomicLong _byteTxnEnqueues = new AtomicLong(0); + private final AtomicLong _msgTxnDequeues = new AtomicLong(0); + private final AtomicLong _byteTxnDequeues = new AtomicLong(0); + private final AtomicLong _unackedMsgCount = new AtomicLong(0); + private final AtomicLong _unackedMsgCountHigh = new AtomicLong(0); + private final AtomicLong _unackedMsgBytes = new AtomicLong(); + + private final AtomicInteger _bindingCountHigh = new AtomicInteger(); + + /** max allowed size(KB) of a single message */ + private long _maximumMessageSize; + + /** max allowed number of messages on a queue. */ + private long _maximumMessageCount; + + /** max queue depth for the queue */ + private long _maximumQueueDepth; + + /** maximum message age before alerts occur */ + private long _maximumMessageAge; + + /** the minimum interval between sending out consecutive alerts of the same type */ + private long _minimumAlertRepeatGap; + + private long _capacity; + + private long _flowResumeCapacity; + + private final Set _notificationChecks = EnumSet.noneOf(NotificationCheck.class); + + + static final int MAX_ASYNC_DELIVERIES = 80; + + + private final AtomicLong _stateChangeCount = new AtomicLong(Long.MIN_VALUE); + + private final Executor _asyncDelivery; + private AtomicInteger _deliveredMessages = new AtomicInteger(); + private AtomicBoolean _stopped = new AtomicBoolean(false); + + private final Set _blockedChannels = new ConcurrentSkipListSet(); + + private final AtomicBoolean _deleted = new AtomicBoolean(false); + private final List _deleteTaskList = new CopyOnWriteArrayList(); + + + private LogSubject _logSubject; + private LogActor _logActor; + + private static final String SUB_FLUSH_RUNNER = "SUB_FLUSH_RUNNER"; + private boolean _nolocal; + + private final AtomicBoolean _overfull = new AtomicBoolean(false); + private boolean _deleteOnNoConsumers; + private final CopyOnWriteArrayList _bindings = new CopyOnWriteArrayList(); + private UUID _id; + private final Map _arguments; + + //TODO : persist creation time + private long _createTime = System.currentTimeMillis(); + + /** the maximum delivery count for each message on this queue or 0 if maximum delivery count is not to be enforced. */ + private int _maximumDeliveryCount; + private final MessageGroupManager _messageGroupManager; + + private final Collection _subscriptionListeners = + new ArrayList(); + + private AMQQueue.NotificationListener _notificationListener; + private final long[] _lastNotificationTimes = new long[NotificationCheck.values().length]; + + + public SimpleAMQQueue(UUID id, String queueName, boolean durable, String owner, boolean autoDelete, boolean exclusive, VirtualHost virtualHost, Map arguments) + { + this(id, queueName, durable, owner, autoDelete, exclusive, virtualHost, new SimpleQueueEntryList.Factory(), arguments); + } + + protected SimpleAMQQueue(UUID id, + String name, + boolean durable, + String owner, + boolean autoDelete, + boolean exclusive, + VirtualHost virtualHost, + QueueEntryListFactory entryListFactory, Map arguments) + { + + 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; + _exclusive = exclusive; + _virtualHost = virtualHost; + _entries = entryListFactory.createQueueEntryList(this); + _arguments = Collections.synchronizedMap(arguments == null ? new LinkedHashMap() : new LinkedHashMap(arguments)); + + _id = id; + _asyncDelivery = ReferenceCountingExecutorService.getInstance().acquireExecutorService(); + + _logSubject = new QueueLogSubject(this); + _logActor = new QueueActor(this, CurrentActor.get().getRootMessageLogger()); + + // Log the creation of this Queue. + // The priorities display is toggled on if we set priorities > 0 + CurrentActor.get().message(_logSubject, + QueueMessages.CREATED(String.valueOf(_owner), + _entries.getPriorities(), + _owner != null, + autoDelete, + durable, !durable, + _entries.getPriorities() > 0)); + + if(arguments != null && arguments.containsKey(Queue.MESSAGE_GROUP_KEY)) + { + if(arguments.get(Queue.MESSAGE_GROUP_SHARED_GROUPS) != null + && (Boolean)(arguments.get(Queue.MESSAGE_GROUP_SHARED_GROUPS))) + { + Object defaultGroup = arguments.get(Queue.MESSAGE_GROUP_DEFAULT_GROUP); + _messageGroupManager = + new DefinedGroupMessageGroupManager(String.valueOf(arguments.get(Queue.MESSAGE_GROUP_KEY)), + defaultGroup == null ? DEFAULT_SHARED_MESSAGE_GROUP : defaultGroup.toString(), + this); + } + else + { + _messageGroupManager = new AssignedSubscriptionMessageGroupManager(String.valueOf(arguments.get( + Queue.MESSAGE_GROUP_KEY)), DEFAULT_MAX_GROUPS); + } + } + else + { + _messageGroupManager = null; + } + + resetNotifications(); + + } + + public void resetNotifications() + { + // This ensure that the notification checks for the configured alerts are created. + setMaximumMessageAge(_maximumMessageAge); + setMaximumMessageCount(_maximumMessageCount); + setMaximumMessageSize(_maximumMessageSize); + setMaximumQueueDepth(_maximumQueueDepth); + } + + // ------ Getters and Setters + + public void execute(Runnable runnable) + { + try + { + _asyncDelivery.execute(runnable); + } + catch (RejectedExecutionException ree) + { + if (_stopped.get()) + { + // Ignore - SubFlusherRunner or QueueRunner submitted execution as queue was being stopped. + } + else + { + _logger.error("Unexpected rejected execution", ree); + throw ree; + } + } + } + + public void setNoLocal(boolean nolocal) + { + _nolocal = nolocal; + } + + public UUID getId() + { + return _id; + } + + public boolean isDurable() + { + return _durable; + } + + public boolean isExclusive() + { + return _exclusive; + } + + public void setExclusive(boolean exclusive) + { + _exclusive = exclusive; + } + + public Exchange getAlternateExchange() + { + return _alternateExchange; + } + + public void setAlternateExchange(Exchange exchange) + { + if(_alternateExchange != null) + { + _alternateExchange.removeReference(this); + } + if(exchange != null) + { + exchange.addReference(this); + } + _alternateExchange = exchange; + } + + + @Override + public Collection getAvailableAttributes() + { + return new ArrayList(_arguments.keySet()); + } + + @Override + public Object getAttribute(String attrName) + { + return _arguments.get(attrName); + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public String getOwner() + { + return _owner; + } + + public AuthorizationHolder getAuthorizationHolder() + { + return _authorizationHolder; + } + + public void setAuthorizationHolder(final AuthorizationHolder authorizationHolder) + { + _authorizationHolder = authorizationHolder; + } + + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public String getName() + { + return _name; + } + + // ------ Manage Subscriptions + + public synchronized void registerSubscription(final Subscription subscription, final boolean exclusive) + throws AMQSecurityException, ExistingExclusiveSubscription, ExistingSubscriptionPreventsExclusive + { + // Access control + if (!getVirtualHost().getSecurityManager().authoriseConsume(this)) + { + throw new AMQSecurityException("Permission denied"); + } + + + if (hasExclusiveSubscriber()) + { + throw new ExistingExclusiveSubscription(); + } + + if (exclusive && !subscription.isTransient()) + { + if (getConsumerCount() != 0) + { + throw new ExistingSubscriptionPreventsExclusive(); + } + else + { + _exclusiveSubscriber = subscription; + } + } + + if(subscription.isActive()) + { + _activeSubscriberCount.incrementAndGet(); + } + subscription.setStateListener(this); + subscription.setQueueContext(new QueueContext(_entries.getHead())); + + if (!isDeleted()) + { + subscription.setQueue(this, exclusive); + if(_nolocal) + { + subscription.setNoLocal(_nolocal); + } + + synchronized (_subscriptionListeners) + { + for(SubscriptionRegistrationListener listener : _subscriptionListeners) + { + listener.subscriptionRegistered(this, subscription); + } + } + + _subscriptionList.add(subscription); + + //Increment consumerCountHigh if necessary. (un)registerSubscription are both + //synchronized methods so we don't need additional synchronization here + if(_counsumerCountHigh.get() < getConsumerCount()) + { + _counsumerCountHigh.incrementAndGet(); + } + + if (isDeleted()) + { + subscription.queueDeleted(this); + } + } + else + { + // TODO + } + + deliverAsync(subscription); + + } + + public synchronized void unregisterSubscription(final Subscription subscription) throws AMQException + { + if (subscription == null) + { + throw new NullPointerException("subscription argument is null"); + } + + boolean removed = _subscriptionList.remove(subscription); + + if (removed) + { + subscription.close(); + // No longer can the queue have an exclusive consumer + setExclusiveSubscriber(null); + subscription.setQueueContext(null); + + if(_messageGroupManager != null) + { + resetSubPointersForGroups(subscription, true); + } + + synchronized (_subscriptionListeners) + { + for(SubscriptionRegistrationListener listener : _subscriptionListeners) + { + listener.subscriptionUnregistered(this, subscription); + } + } + + // auto-delete queues must be deleted if there are no remaining subscribers + + if (_autoDelete && getDeleteOnNoConsumers() && !subscription.isTransient() && getConsumerCount() == 0 ) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Auto-deleteing queue:" + this); + } + + getVirtualHost().removeQueue(this); + + // we need to manually fire the event to the removed subscription (which was the last one left for this + // queue. This is because the delete method uses the subscription set which has just been cleared + subscription.queueDeleted(this); + } + } + + } + + public Collection getConsumers() + { + List consumers = new ArrayList(); + SubscriptionList.SubscriptionNodeIterator iter = _subscriptionList.iterator(); + while(iter.advance()) + { + consumers.add(iter.getNode().getSubscription()); + } + return consumers; + + } + + public void addSubscriptionRegistrationListener(final SubscriptionRegistrationListener listener) + { + synchronized (_subscriptionListeners) + { + _subscriptionListeners.add(listener); + } + } + + public void removeSubscriptionRegistrationListener(final SubscriptionRegistrationListener listener) + { + synchronized (_subscriptionListeners) + { + _subscriptionListeners.remove(listener); + } + } + + public void resetSubPointersForGroups(Subscription subscription, boolean clearAssignments) + { + QueueEntry entry = _messageGroupManager.findEarliestAssignedAvailableEntry(subscription); + if(clearAssignments) + { + _messageGroupManager.clearAssignments(subscription); + } + + if(entry != null) + { + SubscriptionList.SubscriptionNodeIterator subscriberIter = _subscriptionList.iterator(); + // iterate over all the subscribers, and if they are in advance of this queue entry then move them backwards + while (subscriberIter.advance()) + { + Subscription sub = subscriberIter.getNode().getSubscription(); + + // we don't make browsers send the same stuff twice + if (sub.seesRequeues()) + { + updateSubRequeueEntry(sub, entry); + } + } + + deliverAsync(); + + } + } + + public boolean getDeleteOnNoConsumers() + { + return _deleteOnNoConsumers; + } + + public void setDeleteOnNoConsumers(boolean b) + { + _deleteOnNoConsumers = b; + } + + public void addBinding(final Binding binding) + { + _bindings.add(binding); + int bindingCount = _bindings.size(); + int bindingCountHigh; + while(bindingCount > (bindingCountHigh = _bindingCountHigh.get())) + { + if(_bindingCountHigh.compareAndSet(bindingCountHigh, bindingCount)) + { + break; + } + } + } + + public int getBindingCountHigh() + { + return _bindingCountHigh.get(); + } + + public void removeBinding(final Binding binding) + { + _bindings.remove(binding); + } + + public List getBindings() + { + return Collections.unmodifiableList(_bindings); + } + + public int getBindingCount() + { + return getBindings().size(); + } + + public LogSubject getLogSubject() + { + return _logSubject; + } + + // ------ Enqueue / Dequeue + public void enqueue(ServerMessage message) throws AMQException + { + enqueue(message, null); + } + + public void enqueue(ServerMessage message, PostEnqueueAction action) throws AMQException + { + enqueue(message, false, action); + } + + public void enqueue(ServerMessage message, boolean transactional, PostEnqueueAction action) throws AMQException + { + + if(transactional) + { + incrementTxnEnqueueStats(message); + } + incrementQueueCount(); + incrementQueueSize(message); + + _totalMessagesReceived.incrementAndGet(); + + + QueueEntry entry; + final Subscription exclusiveSub = _exclusiveSubscriber; + entry = _entries.add(message); + + if(action != null || (exclusiveSub == null && _queueRunner.isIdle())) + { + /* + + iterate over subscriptions and if any is at the end of the queue and can deliver this message, then deliver the message + + */ + SubscriptionList.SubscriptionNode node = _subscriptionList.getMarkedNode(); + SubscriptionList.SubscriptionNode nextNode = node.findNext(); + if (nextNode == null) + { + nextNode = _subscriptionList.getHead().findNext(); + } + while (nextNode != null) + { + if (_subscriptionList.updateMarkedNode(node, nextNode)) + { + break; + } + else + { + node = _subscriptionList.getMarkedNode(); + nextNode = node.findNext(); + if (nextNode == null) + { + nextNode = _subscriptionList.getHead().findNext(); + } + } + } + + // always do one extra loop after we believe we've finished + // this catches the case where we *just* miss an update + int loops = 2; + + while (entry.isAvailable() && loops != 0) + { + if (nextNode == null) + { + loops--; + nextNode = _subscriptionList.getHead(); + } + else + { + // if subscription at end, and active, offer + Subscription sub = nextNode.getSubscription(); + deliverToSubscription(sub, entry); + } + nextNode = nextNode.findNext(); + + } + } + + + if (entry.isAvailable()) + { + checkSubscriptionsNotAheadOfDelivery(entry); + + if (exclusiveSub != null) + { + deliverAsync(exclusiveSub); + } + else + { + deliverAsync(); + } + } + + checkForNotification(entry.getMessage()); + + if(action != null) + { + action.onEnqueue(entry); + } + + } + + private void deliverToSubscription(final Subscription sub, final QueueEntry entry) + throws AMQException + { + + if(sub.trySendLock()) + { + try + { + if (!sub.isSuspended() + && subscriptionReadyAndHasInterest(sub, entry) + && mightAssign(sub, entry) + && !sub.wouldSuspend(entry)) + { + if (sub.acquires() && !(assign(sub, entry) && entry.acquire(sub))) + { + // restore credit here that would have been taken away by wouldSuspend since we didn't manage + // to acquire the entry for this subscription + sub.restoreCredit(entry); + } + else + { + deliverMessage(sub, entry, false); + } + } + } + finally + { + sub.releaseSendLock(); + } + } + } + + private boolean assign(final Subscription sub, final QueueEntry entry) + { + return _messageGroupManager == null || _messageGroupManager.acceptMessage(sub, entry); + } + + + private boolean mightAssign(final Subscription sub, final QueueEntry entry) + { + if(_messageGroupManager == null || !sub.acquires()) + { + return true; + } + Subscription assigned = _messageGroupManager.getAssignedSubscription(entry); + return (assigned == null) || (assigned == sub); + } + + protected void checkSubscriptionsNotAheadOfDelivery(final QueueEntry entry) + { + // This method is only required for queues which mess with ordering + // Simple Queues don't :-) + } + + private void incrementQueueSize(final ServerMessage message) + { + long size = message.getSize(); + getAtomicQueueSize().addAndGet(size); + _enqueueCount.incrementAndGet(); + _enqueueSize.addAndGet(size); + if(message.isPersistent() && isDurable()) + { + _persistentMessageEnqueueSize.addAndGet(size); + _persistentMessageEnqueueCount.incrementAndGet(); + } + } + + public long getTotalDequeueCount() + { + return _dequeueCount.get(); + } + + public long getTotalEnqueueCount() + { + return _enqueueCount.get(); + } + + private void incrementQueueCount() + { + getAtomicQueueCount().incrementAndGet(); + } + + private void incrementTxnEnqueueStats(final ServerMessage message) + { + _msgTxnEnqueues.incrementAndGet(); + _byteTxnEnqueues.addAndGet(message.getSize()); + } + + private void incrementTxnDequeueStats(QueueEntry entry) + { + _msgTxnDequeues.incrementAndGet(); + _byteTxnDequeues.addAndGet(entry.getSize()); + } + + private void deliverMessage(final Subscription sub, final QueueEntry entry, boolean batch) + throws AMQException + { + setLastSeenEntry(sub, entry); + + _deliveredMessages.incrementAndGet(); + incrementUnackedMsgCount(entry); + + sub.send(entry, batch); + } + + private boolean subscriptionReadyAndHasInterest(final Subscription sub, final QueueEntry entry) throws AMQException + { + return sub.hasInterest(entry) && (getNextAvailableEntry(sub) == entry); + } + + + private void setLastSeenEntry(final Subscription sub, final QueueEntry entry) + { + QueueContext subContext = (QueueContext) sub.getQueueContext(); + if (subContext != null) + { + QueueEntry releasedEntry = subContext.getReleasedEntry(); + + QueueContext._lastSeenUpdater.set(subContext, entry); + if(releasedEntry == entry) + { + QueueContext._releasedUpdater.compareAndSet(subContext, releasedEntry, null); + } + } + } + + private void updateSubRequeueEntry(final Subscription sub, final QueueEntry entry) + { + + QueueContext subContext = (QueueContext) sub.getQueueContext(); + if(subContext != null) + { + QueueEntry oldEntry; + + while((oldEntry = subContext.getReleasedEntry()) == null || oldEntry.compareTo(entry) > 0) + { + if(QueueContext._releasedUpdater.compareAndSet(subContext, oldEntry, entry)) + { + break; + } + } + } + } + + public void requeue(QueueEntry entry) + { + SubscriptionList.SubscriptionNodeIterator subscriberIter = _subscriptionList.iterator(); + // iterate over all the subscribers, and if they are in advance of this queue entry then move them backwards + while (subscriberIter.advance() && entry.isAvailable()) + { + Subscription sub = subscriberIter.getNode().getSubscription(); + + // we don't make browsers send the same stuff twice + if (sub.seesRequeues()) + { + updateSubRequeueEntry(sub, entry); + } + } + + deliverAsync(); + + } + + public void dequeue(QueueEntry entry, Subscription sub) + { + decrementQueueCount(); + decrementQueueSize(entry); + if (entry.acquiredBySubscription()) + { + _deliveredMessages.decrementAndGet(); + } + + if(sub != null && sub.isSessionTransactional()) + { + incrementTxnDequeueStats(entry); + } + + checkCapacity(); + + } + + private void decrementQueueSize(final QueueEntry entry) + { + final ServerMessage message = entry.getMessage(); + long size = message.getSize(); + getAtomicQueueSize().addAndGet(-size); + _dequeueSize.addAndGet(size); + if(message.isPersistent() && isDurable()) + { + _persistentMessageDequeueSize.addAndGet(size); + _persistentMessageDequeueCount.incrementAndGet(); + } + } + + void decrementQueueCount() + { + getAtomicQueueCount().decrementAndGet(); + _dequeueCount.incrementAndGet(); + } + + public boolean resend(final QueueEntry entry, final Subscription subscription) throws AMQException + { + /* TODO : This is wrong as the subscription may be suspended, we should instead change the state of the message + entry to resend and move back the subscription pointer. */ + + subscription.getSendLock(); + try + { + if (!subscription.isClosed()) + { + deliverMessage(subscription, entry, false); + return true; + } + else + { + return false; + } + } + finally + { + subscription.releaseSendLock(); + } + } + + + + public int getConsumerCount() + { + return _subscriptionList.size(); + } + + public int getConsumerCountHigh() + { + return _counsumerCountHigh.get(); + } + + public int getActiveConsumerCount() + { + return _activeSubscriberCount.get(); + } + + public boolean isUnused() + { + return getConsumerCount() == 0; + } + + public boolean isEmpty() + { + return getMessageCount() == 0; + } + + public int getMessageCount() + { + return getAtomicQueueCount().get(); + } + + public long getQueueDepth() + { + return getAtomicQueueSize().get(); + } + + public int getUndeliveredMessageCount() + { + int count = getMessageCount() - _deliveredMessages.get(); + if (count < 0) + { + return 0; + } + else + { + return count; + } + } + + public long getReceivedMessageCount() + { + return _totalMessagesReceived.get(); + } + + public long getOldestMessageArrivalTime() + { + QueueEntry entry = getOldestQueueEntry(); + return entry == null ? Long.MAX_VALUE : entry.getMessage().getArrivalTime(); + } + + protected QueueEntry getOldestQueueEntry() + { + return _entries.next(_entries.getHead()); + } + + public boolean isDeleted() + { + return _deleted.get(); + } + + public List getMessagesOnTheQueue() + { + ArrayList entryList = new ArrayList(); + QueueEntryIterator queueListIterator = _entries.iterator(); + while (queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + if (node != null && !node.isDispensed()) + { + entryList.add(node); + } + } + return entryList; + + } + + public void stateChange(Subscription sub, Subscription.State oldState, Subscription.State newState) + { + if (oldState == Subscription.State.ACTIVE && newState != Subscription.State.ACTIVE) + { + _activeSubscriberCount.decrementAndGet(); + + } + else if (newState == Subscription.State.ACTIVE) + { + if (oldState != Subscription.State.ACTIVE) + { + _activeSubscriberCount.incrementAndGet(); + + } + deliverAsync(sub); + } + } + + public int compareTo(final AMQQueue o) + { + return _name.compareTo(o.getName()); + } + + public AtomicInteger getAtomicQueueCount() + { + return _atomicQueueCount; + } + + public AtomicLong getAtomicQueueSize() + { + return _atomicQueueSize; + } + + public boolean hasExclusiveSubscriber() + { + return _exclusiveSubscriber != null; + } + + private void setExclusiveSubscriber(Subscription exclusiveSubscriber) + { + _exclusiveSubscriber = exclusiveSubscriber; + } + + long getStateChangeCount() + { + return _stateChangeCount.get(); + } + + /** Used to track bindings to exchanges so that on deletion they can easily be cancelled. */ + protected QueueEntryList getEntries() + { + return _entries; + } + + protected SubscriptionList getSubscriptionList() + { + return _subscriptionList; + } + + + public static interface QueueEntryFilter + { + public boolean accept(QueueEntry entry); + + public boolean filterComplete(); + } + + + + public List getMessagesOnTheQueue(final long fromMessageId, final long toMessageId) + { + return getMessagesOnTheQueue(new QueueEntryFilter() + { + + public boolean accept(QueueEntry entry) + { + final long messageId = entry.getMessage().getMessageNumber(); + return messageId >= fromMessageId && messageId <= toMessageId; + } + + public boolean filterComplete() + { + return false; + } + }); + } + + public QueueEntry getMessageOnTheQueue(final long messageId) + { + List entries = getMessagesOnTheQueue(new QueueEntryFilter() + { + private boolean _complete; + + public boolean accept(QueueEntry entry) + { + _complete = entry.getMessage().getMessageNumber() == messageId; + return _complete; + } + + public boolean filterComplete() + { + return _complete; + } + }); + return entries.isEmpty() ? null : entries.get(0); + } + + public List getMessagesOnTheQueue(QueueEntryFilter filter) + { + ArrayList entryList = new ArrayList(); + QueueEntryIterator queueListIterator = _entries.iterator(); + while (queueListIterator.advance() && !filter.filterComplete()) + { + QueueEntry node = queueListIterator.getNode(); + if (!node.isDispensed() && filter.accept(node)) + { + entryList.add(node); + } + } + return entryList; + + } + + public void visit(final QueueEntryVisitor visitor) + { + QueueEntryIterator queueListIterator = _entries.iterator(); + + while(queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + + if(!node.isDispensed()) + { + if(visitor.visit(node)) + { + break; + } + } + } + } + + /** + * Returns a list of QueEntries from a given range of queue positions, eg messages 5 to 10 on the queue. + * + * The 'queue position' index starts from 1. Using 0 in 'from' will be ignored and continue from 1. + * Using 0 in the 'to' field will return an empty list regardless of the 'from' value. + * @param fromPosition + * @param toPosition + * @return + */ + public List getMessagesRangeOnTheQueue(final long fromPosition, final long toPosition) + { + return getMessagesOnTheQueue(new QueueEntryFilter() + { + private long position = 0; + + public boolean accept(QueueEntry entry) + { + position++; + return (position >= fromPosition) && (position <= toPosition); + } + + public boolean filterComplete() + { + return position >= toPosition; + } + }); + + } + + public void purge(final long request) throws AMQException + { + clear(request); + } + + public long getCreateTime() + { + return _createTime; + } + + // ------ Management functions + + // TODO - now only used by the tests + public void deleteMessageFromTop() + { + QueueEntryIterator queueListIterator = _entries.iterator(); + boolean noDeletes = true; + + while (noDeletes && queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + if (node.acquire()) + { + dequeueEntry(node); + noDeletes = false; + } + + } + } + + public long clearQueue() throws AMQException + { + return clear(0l); + } + + private long clear(final long request) throws AMQSecurityException + { + //Perform ACLs + if (!getVirtualHost().getSecurityManager().authorisePurge(this)) + { + throw new AMQSecurityException("Permission denied: queue " + getName()); + } + + QueueEntryIterator queueListIterator = _entries.iterator(); + long count = 0; + + ServerTransaction txn = new LocalTransaction(getVirtualHost().getMessageStore()); + + while (queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + if (node.acquire()) + { + dequeueEntry(node, txn); + if(++count == request) + { + break; + } + } + + } + + txn.commit(); + + return count; + } + + private void dequeueEntry(final QueueEntry node) + { + ServerTransaction txn = new AutoCommitTransaction(getVirtualHost().getMessageStore()); + dequeueEntry(node, txn); + } + + private void dequeueEntry(final QueueEntry node, ServerTransaction txn) + { + txn.dequeue(this, node.getMessage(), + new ServerTransaction.Action() + { + + public void postCommit() + { + node.discard(); + } + + public void onRollback() + { + + } + }); + } + + public void addQueueDeleteTask(final Task task) + { + _deleteTaskList.add(task); + } + + public void removeQueueDeleteTask(final Task task) + { + _deleteTaskList.remove(task); + } + + // TODO list all thrown exceptions + public int delete() throws AMQSecurityException, AMQException + { + // Check access + if (!_virtualHost.getSecurityManager().authoriseDelete(this)) + { + throw new AMQSecurityException("Permission denied: " + getName()); + } + + if (!_deleted.getAndSet(true)) + { + + for (Binding b : _bindings) + { + b.getExchange().removeBinding(b); + } + + SubscriptionList.SubscriptionNodeIterator subscriptionIter = _subscriptionList.iterator(); + + while (subscriptionIter.advance()) + { + Subscription s = subscriptionIter.getNode().getSubscription(); + if (s != null) + { + s.queueDeleted(this); + } + } + + + List entries = getMessagesOnTheQueue(new QueueEntryFilter() + { + + public boolean accept(QueueEntry entry) + { + return entry.acquire(); + } + + public boolean filterComplete() + { + return false; + } + }); + + ServerTransaction txn = new LocalTransaction(getVirtualHost().getMessageStore()); + + if(_alternateExchange != null) + { + + InboundMessageAdapter adapter = new InboundMessageAdapter(); + for(final QueueEntry entry : entries) + { + adapter.setEntry(entry); + List queues = _alternateExchange.route(adapter); + if((queues == null || queues.size() == 0) && _alternateExchange.getAlternateExchange() != null) + { + queues = _alternateExchange.getAlternateExchange().route(adapter); + } + + final ServerMessage message = entry.getMessage(); + if(queues != null && queues.size() != 0) + { + final List rerouteQueues = queues; + txn.enqueue(rerouteQueues, entry.getMessage(), + new ServerTransaction.Action() + { + + public void postCommit() + { + try + { + for(BaseQueue queue : rerouteQueues) + { + queue.enqueue(message); + } + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + + } + + public void onRollback() + { + + } + }); + txn.dequeue(this, entry.getMessage(), + new ServerTransaction.Action() + { + + public void postCommit() + { + entry.discard(); + } + + public void onRollback() + { + } + }); + } + + } + + _alternateExchange.removeReference(this); + } + else + { + // TODO log discard + + for(final QueueEntry entry : entries) + { + final ServerMessage message = entry.getMessage(); + if(message != null) + { + txn.dequeue(this, message, + new ServerTransaction.Action() + { + + public void postCommit() + { + entry.discard(); + } + + public void onRollback() + { + } + }); + } + } + } + + txn.commit(); + + for (Task task : _deleteTaskList) + { + task.doTask(this); + } + + _deleteTaskList.clear(); + stop(); + + //Log Queue Deletion + CurrentActor.get().message(_logSubject, QueueMessages.DELETED()); + + } + return getMessageCount(); + + } + + public void stop() + { + if (!_stopped.getAndSet(true)) + { + ReferenceCountingExecutorService.getInstance().releaseExecutorService(); + } + } + + public void checkCapacity(AMQSessionModel channel) + { + if(_capacity != 0l) + { + if(_atomicQueueSize.get() > _capacity) + { + _overfull.set(true); + //Overfull log message + _logActor.message(_logSubject, QueueMessages.OVERFULL(_atomicQueueSize.get(), _capacity)); + + _blockedChannels.add(channel); + + channel.block(this); + + if(_atomicQueueSize.get() <= _flowResumeCapacity) + { + + //Underfull log message + _logActor.message(_logSubject, QueueMessages.UNDERFULL(_atomicQueueSize.get(), _flowResumeCapacity)); + + channel.unblock(this); + _blockedChannels.remove(channel); + + } + + } + + + + } + } + + private void checkCapacity() + { + if(_capacity != 0L) + { + if(_overfull.get() && _atomicQueueSize.get() <= _flowResumeCapacity) + { + if(_overfull.compareAndSet(true,false)) + {//Underfull log message + _logActor.message(_logSubject, QueueMessages.UNDERFULL(_atomicQueueSize.get(), _flowResumeCapacity)); + } + + for(final AMQSessionModel blockedChannel : _blockedChannels) + { + blockedChannel.unblock(this); + _blockedChannels.remove(blockedChannel); + } + } + } + } + + private QueueRunner _queueRunner = new QueueRunner(this); + + public void deliverAsync() + { + _stateChangeCount.incrementAndGet(); + + _queueRunner.execute(_asyncDelivery); + + } + + public void deliverAsync(Subscription sub) + { + if(_exclusiveSubscriber == null) + { + deliverAsync(); + } + else + { + SubFlushRunner flusher = (SubFlushRunner) sub.get(SUB_FLUSH_RUNNER); + if(flusher == null) + { + flusher = new SubFlushRunner(sub); + sub.set(SUB_FLUSH_RUNNER, flusher); + } + flusher.execute(_asyncDelivery); + } + + } + + public void flushSubscription(Subscription sub) throws AMQException + { + // Access control + if (!getVirtualHost().getSecurityManager().authoriseConsume(this)) + { + throw new AMQSecurityException("Permission denied: " + getName()); + } + flushSubscription(sub, Long.MAX_VALUE); + } + + public boolean flushSubscription(Subscription sub, long iterations) throws AMQException + { + boolean atTail = false; + final boolean keepSendLockHeld = iterations <= SimpleAMQQueue.MAX_ASYNC_DELIVERIES; + boolean queueEmpty = false; + + try + { + if(keepSendLockHeld) + { + sub.getSendLock(); + } + while (!sub.isSuspended() && !atTail && iterations != 0) + { + try + { + if(!keepSendLockHeld) + { + sub.getSendLock(); + } + + atTail = attemptDelivery(sub, true); + if (atTail && getNextAvailableEntry(sub) == null) + { + queueEmpty = true; + } + else if (!atTail) + { + iterations--; + } + } + finally + { + if(!keepSendLockHeld) + { + sub.releaseSendLock(); + } + } + } + } + finally + { + if(keepSendLockHeld) + { + sub.releaseSendLock(); + } + if(queueEmpty) + { + sub.queueEmpty(); + } + + sub.flushBatched(); + + } + + + // if there's (potentially) more than one subscription the others will potentially not have been advanced to the + // next entry they are interested in yet. This would lead to holding on to references to expired messages, etc + // which would give us memory "leak". + + if (!hasExclusiveSubscriber()) + { + advanceAllSubscriptions(); + } + return atTail; + } + + /** + * Attempt delivery for the given subscription. + * + * Looks up the next node for the subscription and attempts to deliver it. + * + * + * @param sub + * @param batch + * @return true if we have completed all possible deliveries for this sub. + * @throws AMQException + */ + private boolean attemptDelivery(Subscription sub, boolean batch) throws AMQException + { + boolean atTail = false; + + boolean subActive = sub.isActive() && !sub.isSuspended(); + if (subActive) + { + + QueueEntry node = getNextAvailableEntry(sub); + + if (node != null && node.isAvailable()) + { + if (sub.hasInterest(node) && mightAssign(sub, node)) + { + if (!sub.wouldSuspend(node)) + { + if (sub.acquires() && !(assign(sub, node) && node.acquire(sub))) + { + // restore credit here that would have been taken away by wouldSuspend since we didn't manage + // to acquire the entry for this subscription + sub.restoreCredit(node); + } + else + { + deliverMessage(sub, node, batch); + } + + } + else // Not enough Credit for message and wouldSuspend + { + //QPID-1187 - Treat the subscription as suspended for this message + // and wait for the message to be removed to continue delivery. + subActive = false; + node.addStateChangeListener(new QueueEntryListener(sub)); + } + } + + } + atTail = (node == null) || (_entries.next(node) == null); + } + return atTail || !subActive; + } + + protected void advanceAllSubscriptions() throws AMQException + { + SubscriptionList.SubscriptionNodeIterator subscriberIter = _subscriptionList.iterator(); + while (subscriberIter.advance()) + { + SubscriptionList.SubscriptionNode subNode = subscriberIter.getNode(); + Subscription sub = subNode.getSubscription(); + if(sub.acquires()) + { + getNextAvailableEntry(sub); + } + else + { + // TODO + } + } + } + + private QueueEntry getNextAvailableEntry(final Subscription sub) + throws AMQException + { + QueueContext context = (QueueContext) sub.getQueueContext(); + if(context != null) + { + QueueEntry lastSeen = context.getLastSeenEntry(); + QueueEntry releasedNode = context.getReleasedEntry(); + + QueueEntry node = (releasedNode != null && lastSeen.compareTo(releasedNode)>=0) ? releasedNode : _entries.next(lastSeen); + + boolean expired = false; + while (node != null && (!node.isAvailable() || (expired = node.expired()) || !sub.hasInterest(node) || + !mightAssign(sub,node))) + { + if (expired) + { + expired = false; + if (node.acquire()) + { + dequeueEntry(node); + } + } + + if(QueueContext._lastSeenUpdater.compareAndSet(context, lastSeen, node)) + { + QueueContext._releasedUpdater.compareAndSet(context, releasedNode, null); + } + + lastSeen = context.getLastSeenEntry(); + releasedNode = context.getReleasedEntry(); + node = (releasedNode != null && lastSeen.compareTo(releasedNode)>0) ? releasedNode : _entries.next(lastSeen); + } + return node; + } + else + { + return null; + } + } + + public boolean isEntryAheadOfSubscription(QueueEntry entry, Subscription sub) + { + QueueContext context = (QueueContext) sub.getQueueContext(); + if(context != null) + { + QueueEntry releasedNode = context.getReleasedEntry(); + return releasedNode != null && releasedNode.compareTo(entry) < 0; + } + else + { + return false; + } + } + + /** + * Used by queue Runners to asynchronously deliver messages to consumers. + * + * A queue Runner is started whenever a state change occurs, e.g when a new + * message arrives on the queue and cannot be immediately delivered to a + * subscription (i.e. asynchronous delivery is required). Unless there are + * SubFlushRunners operating (due to subscriptions unsuspending) which are + * capable of accepting/delivering all messages then these messages would + * otherwise remain on the queue. + * + * processQueue should be running while there are messages on the queue AND + * there are subscriptions that can deliver them. If there are no + * subscriptions capable of delivering the remaining messages on the queue + * then processQueue should stop to prevent spinning. + * + * Since processQueue is runs in a fixed size Executor, it should not run + * indefinitely to prevent starving other tasks of CPU (e.g jobs to process + * incoming messages may not be able to be scheduled in the thread pool + * because all threads are working on clearing down large queues). To solve + * this problem, after an arbitrary number of message deliveries the + * processQueue job stops iterating, resubmits itself to the executor, and + * ends the current instance + * + * @param runner the Runner to schedule + * @throws AMQException + */ + public long processQueue(QueueRunner runner) throws AMQException + { + long stateChangeCount = Long.MIN_VALUE; + long previousStateChangeCount = Long.MIN_VALUE; + long rVal = Long.MIN_VALUE; + boolean deliveryIncomplete = true; + + boolean lastLoop = false; + int iterations = MAX_ASYNC_DELIVERIES; + + final int numSubs = _subscriptionList.size(); + + final int perSub = Math.max(iterations / Math.max(numSubs,1), 1); + + // For every message enqueue/requeue the we fire deliveryAsync() which + // increases _stateChangeCount. If _sCC changes whilst we are in our loop + // (detected by setting previousStateChangeCount to stateChangeCount in the loop body) + // then we will continue to run for a maximum of iterations. + // So whilst delivery/rejection is going on a processQueue thread will be running + while (iterations != 0 && ((previousStateChangeCount != (stateChangeCount = _stateChangeCount.get())) || deliveryIncomplete)) + { + // we want to have one extra loop after every subscription has reached the point where it cannot move + // further, just in case the advance of one subscription in the last loop allows a different subscription to + // move forward in the next iteration + + if (previousStateChangeCount != stateChangeCount) + { + //further asynchronous delivery is required since the + //previous loop. keep going if iteration slicing allows. + lastLoop = false; + rVal = stateChangeCount; + } + + previousStateChangeCount = stateChangeCount; + boolean allSubscriptionsDone = true; + boolean subscriptionDone; + + SubscriptionList.SubscriptionNodeIterator subscriptionIter = _subscriptionList.iterator(); + //iterate over the subscribers and try to advance their pointer + while (subscriptionIter.advance()) + { + Subscription sub = subscriptionIter.getNode().getSubscription(); + sub.getSendLock(); + + try + { + for(int i = 0 ; i < perSub; i++) + { + //attempt delivery. returns true if no further delivery currently possible to this sub + subscriptionDone = attemptDelivery(sub, true); + if (subscriptionDone) + { + sub.flushBatched(); + if (lastLoop && !sub.isSuspended()) + { + sub.queueEmpty(); + } + break; + } + else + { + //this subscription can accept additional deliveries, so we must + //keep going after this (if iteration slicing allows it) + allSubscriptionsDone = false; + lastLoop = false; + if(--iterations == 0) + { + sub.flushBatched(); + break; + } + } + + } + + sub.flushBatched(); + } + finally + { + sub.releaseSendLock(); + } + } + + if(allSubscriptionsDone && lastLoop) + { + //We have done an extra loop already and there are again + //again no further delivery attempts possible, only + //keep going if state change demands it. + deliveryIncomplete = false; + } + else if(allSubscriptionsDone) + { + //All subscriptions reported being done, but we have to do + //an extra loop if the iterations are not exhausted and + //there is still any work to be done + deliveryIncomplete = _subscriptionList.size() != 0; + lastLoop = true; + } + else + { + //some subscriptions can still accept more messages, + //keep going if iteration count allows. + lastLoop = false; + deliveryIncomplete = true; + } + + } + + // If iterations == 0 then the limiting factor was the time-slicing rather than available messages or credit + // therefore we should schedule this runner again (unless someone beats us to it :-) ). + if (iterations == 0) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Rescheduling runner:" + runner); + } + return 0L; + } + return rVal; + + } + + public void checkMessageStatus() throws AMQException + { + QueueEntryIterator queueListIterator = _entries.iterator(); + + while (queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + // Only process nodes that are not currently deleted and not dequeued + if (!node.isDispensed()) + { + // If the node has exired then acquire it + if (node.expired() && node.acquire()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeuing expired node " + node); + } + // Then dequeue it. + dequeueEntry(node); + } + else + { + // There is a chance that the node could be deleted by + // the time the check actually occurs. So verify we + // can actually get the message to perform the check. + ServerMessage msg = node.getMessage(); + if (msg != null) + { + checkForNotification(msg); + } + } + } + } + + } + + public long getMinimumAlertRepeatGap() + { + return _minimumAlertRepeatGap; + } + + public void setMinimumAlertRepeatGap(long minimumAlertRepeatGap) + { + _minimumAlertRepeatGap = minimumAlertRepeatGap; + } + + public long getMaximumMessageAge() + { + return _maximumMessageAge; + } + + public void setMaximumMessageAge(long maximumMessageAge) + { + _maximumMessageAge = maximumMessageAge; + if (maximumMessageAge == 0L) + { + _notificationChecks.remove(NotificationCheck.MESSAGE_AGE_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.MESSAGE_AGE_ALERT); + } + } + + public long getMaximumMessageCount() + { + return _maximumMessageCount; + } + + public void setMaximumMessageCount(final long maximumMessageCount) + { + _maximumMessageCount = maximumMessageCount; + if (maximumMessageCount == 0L) + { + _notificationChecks.remove(NotificationCheck.MESSAGE_COUNT_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.MESSAGE_COUNT_ALERT); + } + + } + + public long getMaximumQueueDepth() + { + return _maximumQueueDepth; + } + + // Sets the queue depth, the max queue size + public void setMaximumQueueDepth(final long maximumQueueDepth) + { + _maximumQueueDepth = maximumQueueDepth; + if (maximumQueueDepth == 0L) + { + _notificationChecks.remove(NotificationCheck.QUEUE_DEPTH_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.QUEUE_DEPTH_ALERT); + } + + } + + public long getMaximumMessageSize() + { + return _maximumMessageSize; + } + + public void setMaximumMessageSize(final long maximumMessageSize) + { + _maximumMessageSize = maximumMessageSize; + if (maximumMessageSize == 0L) + { + _notificationChecks.remove(NotificationCheck.MESSAGE_SIZE_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.MESSAGE_SIZE_ALERT); + } + } + + public long getCapacity() + { + return _capacity; + } + + public void setCapacity(long capacity) + { + _capacity = capacity; + } + + public long getFlowResumeCapacity() + { + return _flowResumeCapacity; + } + + public void setFlowResumeCapacity(long flowResumeCapacity) + { + _flowResumeCapacity = flowResumeCapacity; + + checkCapacity(); + } + + public boolean isOverfull() + { + return _overfull.get(); + } + + public Set getNotificationChecks() + { + return _notificationChecks; + } + + private final class QueueEntryListener implements QueueEntry.StateChangeListener + { + + private final Subscription _sub; + + public QueueEntryListener(final Subscription sub) + { + _sub = sub; + } + + public boolean equals(Object o) + { + return o instanceof SimpleAMQQueue.QueueEntryListener + && _sub == ((QueueEntryListener) o)._sub; + } + + public int hashCode() + { + return System.identityHashCode(_sub); + } + + public void stateChanged(QueueEntry entry, QueueEntry.State oldSate, QueueEntry.State newState) + { + entry.removeStateChangeListener(this); + deliverAsync(_sub); + } + } + + public List getMessagesOnTheQueue(int num) + { + return getMessagesOnTheQueue(num, 0); + } + + public List getMessagesOnTheQueue(int num, int offset) + { + ArrayList ids = new ArrayList(num); + QueueEntryIterator it = _entries.iterator(); + for (int i = 0; i < offset; i++) + { + it.advance(); + } + + for (int i = 0; i < num && !it.atTail(); i++) + { + it.advance(); + ids.add(it.getNode().getMessage().getMessageNumber()); + } + return ids; + } + + public AMQSessionModel getExclusiveOwningSession() + { + return _exclusiveOwner; + } + + public void setExclusiveOwningSession(AMQSessionModel exclusiveOwner) + { + _exclusive = true; + _exclusiveOwner = exclusiveOwner; + } + + + public void configure(QueueConfiguration config) + { + if (config != null) + { + setMaximumMessageAge(config.getMaximumMessageAge()); + setMaximumQueueDepth(config.getMaximumQueueDepth()); + setMaximumMessageSize(config.getMaximumMessageSize()); + setMaximumMessageCount(config.getMaximumMessageCount()); + setMinimumAlertRepeatGap(config.getMinimumAlertRepeatGap()); + setMaximumDeliveryCount(config.getMaxDeliveryCount()); + _capacity = config.getCapacity(); + _flowResumeCapacity = config.getFlowResumeCapacity(); + } + } + + public long getMessageDequeueCount() + { + return _dequeueCount.get(); + } + + public long getTotalEnqueueSize() + { + return _enqueueSize.get(); + } + + public long getTotalDequeueSize() + { + return _dequeueSize.get(); + } + + public long getByteTxnEnqueues() + { + return _byteTxnEnqueues.get(); + } + + public long getByteTxnDequeues() + { + return _byteTxnDequeues.get(); + } + + public long getMsgTxnEnqueues() + { + return _msgTxnEnqueues.get(); + } + + public long getMsgTxnDequeues() + { + return _msgTxnDequeues.get(); + } + + public long getPersistentByteEnqueues() + { + return _persistentMessageEnqueueSize.get(); + } + + public long getPersistentByteDequeues() + { + return _persistentMessageDequeueSize.get(); + } + + public long getPersistentMsgEnqueues() + { + return _persistentMessageEnqueueCount.get(); + } + + public long getPersistentMsgDequeues() + { + return _persistentMessageDequeueCount.get(); + } + + + @Override + public String toString() + { + return getName(); + } + + public long getUnackedMessageCountHigh() + { + return _unackedMsgCountHigh.get(); + } + + public long getUnackedMessageCount() + { + return _unackedMsgCount.get(); + } + + public long getUnackedMessageBytes() + { + return _unackedMsgBytes.get(); + } + + public void decrementUnackedMsgCount(QueueEntry queueEntry) + { + _unackedMsgCount.decrementAndGet(); + _unackedMsgBytes.addAndGet(-queueEntry.getSize()); + } + + private void incrementUnackedMsgCount(QueueEntry entry) + { + long unackedMsgCount = _unackedMsgCount.incrementAndGet(); + _unackedMsgBytes.addAndGet(entry.getSize()); + + long unackedMsgCountHigh; + while(unackedMsgCount > (unackedMsgCountHigh = _unackedMsgCountHigh.get())) + { + if(_unackedMsgCountHigh.compareAndSet(unackedMsgCountHigh, unackedMsgCount)) + { + break; + } + } + } + + public LogActor getLogActor() + { + return _logActor; + } + + public int getMaximumDeliveryCount() + { + return _maximumDeliveryCount; + } + + public void setMaximumDeliveryCount(final int maximumDeliveryCount) + { + _maximumDeliveryCount = maximumDeliveryCount; + } + + /** + * Checks if there is any notification to send to the listeners + */ + private void checkForNotification(ServerMessage msg) throws AMQException + { + final Set notificationChecks = getNotificationChecks(); + final AMQQueue.NotificationListener listener = _notificationListener; + + if(listener != null && !notificationChecks.isEmpty()) + { + final long currentTime = System.currentTimeMillis(); + final long thresholdTime = currentTime - getMinimumAlertRepeatGap(); + + for (NotificationCheck check : notificationChecks) + { + if (check.isMessageSpecific() || (_lastNotificationTimes[check.ordinal()] < thresholdTime)) + { + if (check.notifyIfNecessary(msg, this, listener)) + { + _lastNotificationTimes[check.ordinal()] = currentTime; + } + } + } + } + } + + public void setNotificationListener(AMQQueue.NotificationListener listener) + { + _notificationListener = listener; + } + + @Override + public void setDescription(String description) + { + if (description == null) + { + _arguments.remove(Queue.DESCRIPTION); + } + else + { + _arguments.put(Queue.DESCRIPTION, description); + } + } + + @Override + public String getDescription() + { + return (String) _arguments.get(Queue.DESCRIPTION); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryImpl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryImpl.java new file mode 100644 index 0000000000..4a10d31d37 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryImpl.java @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.message.ServerMessage; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +public class SimpleQueueEntryImpl extends QueueEntryImpl +{ + static final AtomicReferenceFieldUpdater + _nextUpdater = + AtomicReferenceFieldUpdater.newUpdater + (SimpleQueueEntryImpl.class, SimpleQueueEntryImpl.class, "_next"); + + private volatile SimpleQueueEntryImpl _next; + + public SimpleQueueEntryImpl(SimpleQueueEntryList queueEntryList) + { + super(queueEntryList); + } + + public SimpleQueueEntryImpl(SimpleQueueEntryList queueEntryList, ServerMessage message, final long entryId) + { + super(queueEntryList, message, entryId); + } + + public SimpleQueueEntryImpl(SimpleQueueEntryList queueEntryList, ServerMessage message) + { + super(queueEntryList, message); + } + + public SimpleQueueEntryImpl getNextNode() + { + return _next; + } + + public SimpleQueueEntryImpl getNextValidEntry() + { + + SimpleQueueEntryImpl next = getNextNode(); + while(next != null && next.isDispensed()) + { + + final SimpleQueueEntryImpl newNext = next.getNextNode(); + if(newNext != null) + { + SimpleQueueEntryList._nextUpdater.compareAndSet(this,next, newNext); + next = getNextNode(); + } + else + { + next = null; + } + + } + return next; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java new file mode 100644 index 0000000000..b8d8ec19f4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java @@ -0,0 +1,205 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* 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.message.ServerMessage; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +public class SimpleQueueEntryList implements QueueEntryList +{ + + private final SimpleQueueEntryImpl _head; + + private volatile SimpleQueueEntryImpl _tail; + + static final AtomicReferenceFieldUpdater + _tailUpdater = + AtomicReferenceFieldUpdater.newUpdater + (SimpleQueueEntryList.class, SimpleQueueEntryImpl.class, "_tail"); + + + private final AMQQueue _queue; + + static final AtomicReferenceFieldUpdater + _nextUpdater = SimpleQueueEntryImpl._nextUpdater; + + private AtomicLong _scavenges = new AtomicLong(0L); + private final long _scavengeCount = Integer.getInteger("qpid.queue.scavenge_count", 50); + private final AtomicReference _unscavengedHWM = new AtomicReference(); + + + public SimpleQueueEntryList(AMQQueue queue) + { + _queue = queue; + _head = new SimpleQueueEntryImpl(this); + _tail = _head; + } + + void scavenge() + { + SimpleQueueEntryImpl hwm = _unscavengedHWM.getAndSet(null); + SimpleQueueEntryImpl next = _head.getNextValidEntry(); + + if(hwm != null) + { + while (next != null && hwm.compareTo(next)>0) + { + next = next.getNextValidEntry(); + } + } + } + + + public AMQQueue getQueue() + { + return _queue; + } + + + public SimpleQueueEntryImpl add(ServerMessage message) + { + SimpleQueueEntryImpl node = createQueueEntry(message); + for (;;) + { + SimpleQueueEntryImpl tail = _tail; + SimpleQueueEntryImpl next = tail.getNextNode(); + if (tail == _tail) + { + if (next == null) + { + node.setEntryId(tail.getEntryId()+1); + if (_nextUpdater.compareAndSet(tail, null, node)) + { + _tailUpdater.compareAndSet(this, tail, node); + + return node; + } + } + else + { + _tailUpdater.compareAndSet(this,tail, next); + } + } + } + } + + protected SimpleQueueEntryImpl createQueueEntry(ServerMessage message) + { + return new SimpleQueueEntryImpl(this, message); + } + + public SimpleQueueEntryImpl next(SimpleQueueEntryImpl node) + { + return node.getNextValidEntry(); + } + + public static class QueueEntryIteratorImpl implements QueueEntryIterator + { + private SimpleQueueEntryImpl _lastNode; + + QueueEntryIteratorImpl(SimpleQueueEntryImpl startNode) + { + _lastNode = startNode; + } + + public boolean atTail() + { + return _lastNode.getNextValidEntry() == null; + } + + public SimpleQueueEntryImpl getNode() + { + return _lastNode; + } + + public boolean advance() + { + SimpleQueueEntryImpl nextValidNode = _lastNode.getNextValidEntry(); + + if(nextValidNode != null) + { + _lastNode = nextValidNode; + } + + return nextValidNode != null; + } + } + + public QueueEntryIteratorImpl iterator() + { + return new QueueEntryIteratorImpl(_head); + } + + + public SimpleQueueEntryImpl getHead() + { + return _head; + } + + public void entryDeleted(SimpleQueueEntryImpl queueEntry) + { + SimpleQueueEntryImpl next = _head.getNextNode(); + SimpleQueueEntryImpl newNext = _head.getNextValidEntry(); + + // the head of the queue has not been deleted, hence the deletion must have been mid queue. + if (next == newNext) + { + SimpleQueueEntryImpl unscavengedHWM = _unscavengedHWM.get(); + while(unscavengedHWM == null || unscavengedHWM.compareTo(queueEntry)<0) + { + _unscavengedHWM.compareAndSet(unscavengedHWM, queueEntry); + unscavengedHWM = _unscavengedHWM.get(); + } + if (_scavenges.incrementAndGet() > _scavengeCount) + { + _scavenges.set(0L); + scavenge(); + } + } + else + { + SimpleQueueEntryImpl unscavengedHWM = _unscavengedHWM.get(); + if(unscavengedHWM != null && (next == null || unscavengedHWM.compareTo(next) < 0)) + { + _unscavengedHWM.compareAndSet(unscavengedHWM, null); + } + } + } + + public int getPriorities() + { + return 0; + } + + static class Factory implements QueueEntryListFactory + { + + public SimpleQueueEntryList createQueueEntryList(AMQQueue queue) + { + return new SimpleQueueEntryList(queue); + } + } + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueue.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueue.java new file mode 100644 index 0000000000..b3566df0c4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueue.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.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Map; +import java.util.UUID; + +public class SortedQueue extends OutOfOrderQueue +{ + //Lock object to synchronize enqueue. Used instead of the object + //monitor to prevent lock order issues with subscription sendLocks + //and consumer updates in the super classes + private final Object _sortedQueueLock = new Object(); + private final String _sortedPropertyName; + + protected SortedQueue(UUID id, final String name, + final boolean durable, final String owner, final boolean autoDelete, + final boolean exclusive, final VirtualHost virtualHost, Map arguments, String sortedPropertyName) + { + super(id, name, durable, owner, autoDelete, exclusive, + virtualHost, new SortedQueueEntryListFactory(sortedPropertyName), arguments); + this._sortedPropertyName = sortedPropertyName; + } + + public String getSortedPropertyName() + { + return _sortedPropertyName; + } + + public void enqueue(ServerMessage message, PostEnqueueAction action) throws AMQException + { + synchronized (_sortedQueueLock) + { + super.enqueue(message, action); + } + } +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryImpl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryImpl.java new file mode 100644 index 0000000000..1052adbe67 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryImpl.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 org.apache.qpid.server.message.ServerMessage; + +/** + * An implementation of QueueEntryImpl to be used in SortedQueueEntryList. + */ +public class SortedQueueEntryImpl extends QueueEntryImpl +{ + public static enum Colour + { + RED, BLACK + }; + + private volatile SortedQueueEntryImpl _next; + private SortedQueueEntryImpl _prev; + private String _key; + + private Colour _colour = Colour.BLACK; + private SortedQueueEntryImpl _parent; + private SortedQueueEntryImpl _left; + private SortedQueueEntryImpl _right; + + public SortedQueueEntryImpl(final SortedQueueEntryList queueEntryList) + { + super(queueEntryList); + } + + public SortedQueueEntryImpl(final SortedQueueEntryList queueEntryList, + final ServerMessage message, final long entryId) + { + super(queueEntryList, message, entryId); + } + + @Override + public int compareTo(final QueueEntry o) + { + final String otherKey = ((SortedQueueEntryImpl) o)._key; + final int compare = _key == null ? (otherKey == null ? 0 : -1) : otherKey == null ? 1 : _key.compareTo(otherKey); + return compare == 0 ? super.compareTo(o) : compare; + } + + public Colour getColour() + { + return _colour; + } + + public String getKey() + { + return _key; + } + + public SortedQueueEntryImpl getLeft() + { + return _left; + } + + public SortedQueueEntryImpl getNextNode() + { + return _next; + } + + @Override + public SortedQueueEntryImpl getNextValidEntry() + { + return getNextNode(); + } + + public SortedQueueEntryImpl getParent() + { + return _parent; + } + + public SortedQueueEntryImpl getPrev() + { + return _prev; + } + + public SortedQueueEntryImpl getRight() + { + return _right; + } + + public void setColour(final Colour colour) + { + _colour = colour; + } + + public void setKey(final String key) + { + _key = key; + } + + public void setLeft(final SortedQueueEntryImpl left) + { + _left = left; + } + + public void setNext(final SortedQueueEntryImpl next) + { + _next = next; + } + + public void setParent(final SortedQueueEntryImpl parent) + { + _parent = parent; + } + + public void setPrev(final SortedQueueEntryImpl prev) + { + _prev = prev; + } + + public void setRight(final SortedQueueEntryImpl right) + { + _right = right; + } + + @Override + public String toString() + { + return "(" + (_colour == Colour.RED ? "Red," : "Black,") + _key + ")"; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryList.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryList.java new file mode 100644 index 0000000000..7f742d455d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryList.java @@ -0,0 +1,659 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.message.ServerMessage; +import org.apache.qpid.server.queue.SortedQueueEntryImpl.Colour; + +/** + * A sorted implementation of QueueEntryList. + * Uses the red/black tree algorithm specified in "Introduction to Algorithms". + * ISBN-10: 0262033844 + * ISBN-13: 978-0262033848 + * @see http://en.wikipedia.org/wiki/Red-black_tree + */ +public class SortedQueueEntryList implements QueueEntryList +{ + private final SortedQueueEntryImpl _head; + private SortedQueueEntryImpl _root; + private long _entryId = Long.MIN_VALUE; + private final Object _lock = new Object(); + private final AMQQueue _queue; + private final String _propertyName; + + public SortedQueueEntryList(final AMQQueue queue, final String propertyName) + { + _queue = queue; + _head = new SortedQueueEntryImpl(this); + _propertyName = propertyName; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public SortedQueueEntryImpl add(final ServerMessage message) + { + synchronized(_lock) + { + String key = null; + final Object val = message.getMessageHeader().getHeader(_propertyName); + if(val != null) + { + key = val.toString(); + } + + final SortedQueueEntryImpl entry = new SortedQueueEntryImpl(this,message, ++_entryId); + entry.setKey(key); + + insert(entry); + + return entry; + } + } + + /** + * Red Black Tree insert implementation. + * @param entry the entry to insert. + */ + private void insert(final SortedQueueEntryImpl entry) + { + SortedQueueEntryImpl node = _root; + if((node = _root) == null) + { + _root = entry; + _head.setNext(entry); + entry.setPrev(_head); + return; + } + else + { + SortedQueueEntryImpl parent = null; + while(node != null) + { + parent = node; + if(entry.compareTo(node) < 0) + { + node = node.getLeft(); + } + else + { + node = node.getRight(); + } + } + entry.setParent(parent); + + if(entry.compareTo(parent) < 0) + { + parent.setLeft(entry); + final SortedQueueEntryImpl prev = parent.getPrev(); + entry.setNext(parent); + prev.setNext(entry); + entry.setPrev(prev); + parent.setPrev(entry); + } + else + { + parent.setRight(entry); + + final SortedQueueEntryImpl next = parent.getNextValidEntry(); + entry.setNext(next); + parent.setNext(entry); + + if(next != null) + { + next.setPrev(entry); + } + entry.setPrev(parent); + } + } + entry.setColour(Colour.RED); + insertFixup(entry); + } + + private void insertFixup(SortedQueueEntryImpl entry) + { + while(isParentColour(entry, Colour.RED)) + { + final SortedQueueEntryImpl grandparent = nodeGrandparent(entry); + + if(nodeParent(entry) == leftChild(grandparent)) + { + final SortedQueueEntryImpl y = rightChild(grandparent); + if(isNodeColour(y, Colour.RED)) + { + setColour(nodeParent(entry), Colour.BLACK); + setColour(y, Colour.BLACK); + setColour(grandparent, Colour.RED); + entry = grandparent; + } + else + { + if(entry == rightChild(nodeParent(entry))) + { + entry = nodeParent(entry); + leftRotate(entry); + } + setColour(nodeParent(entry), Colour.BLACK); + setColour(nodeGrandparent(entry), Colour.RED); + rightRotate(nodeGrandparent(entry)); + } + } + else + { + final SortedQueueEntryImpl y = leftChild(grandparent); + if(isNodeColour(y, Colour.RED)) + { + setColour(nodeParent(entry), Colour.BLACK); + setColour(y, Colour.BLACK); + setColour(grandparent, Colour.RED); + entry = grandparent; + } + else + { + if(entry == leftChild(nodeParent(entry))) + { + entry = nodeParent(entry); + rightRotate(entry); + } + setColour(nodeParent(entry), Colour.BLACK); + setColour(nodeGrandparent(entry), Colour.RED); + leftRotate(nodeGrandparent(entry)); + } + } + } + _root.setColour(Colour.BLACK); + } + + private void leftRotate(final SortedQueueEntryImpl entry) + { + if(entry != null) + { + final SortedQueueEntryImpl rightChild = rightChild(entry); + entry.setRight(rightChild.getLeft()); + if(entry.getRight() != null) + { + entry.getRight().setParent(entry); + } + rightChild.setParent(entry.getParent()); + if(entry.getParent() == null) + { + _root = rightChild; + } + else if(entry == entry.getParent().getLeft()) + { + entry.getParent().setLeft(rightChild); + } + else + { + entry.getParent().setRight(rightChild); + } + rightChild.setLeft(entry); + entry.setParent(rightChild); + } + } + + private void rightRotate(final SortedQueueEntryImpl entry) + { + if(entry != null) + { + final SortedQueueEntryImpl leftChild = leftChild(entry); + entry.setLeft(leftChild.getRight()); + if(entry.getLeft() != null) + { + leftChild.getRight().setParent(entry); + } + leftChild.setParent(entry.getParent()); + if(leftChild.getParent() == null) + { + _root = leftChild; + } + else if(entry == entry.getParent().getRight()) + { + entry.getParent().setRight(leftChild); + } + else + { + entry.getParent().setLeft(leftChild); + } + leftChild.setRight(entry); + entry.setParent(leftChild); + } + } + + private void setColour(final SortedQueueEntryImpl node, final Colour colour) + { + if(node != null) + { + node.setColour(colour); + } + } + + private SortedQueueEntryImpl leftChild(final SortedQueueEntryImpl node) + { + return node == null ? null : node.getLeft(); + } + + private SortedQueueEntryImpl rightChild(final SortedQueueEntryImpl node) + { + return node == null ? null : node.getRight(); + } + + private SortedQueueEntryImpl nodeParent(final SortedQueueEntryImpl node) + { + return node == null ? null : node.getParent(); + } + + private SortedQueueEntryImpl nodeGrandparent(final SortedQueueEntryImpl node) + { + return nodeParent(nodeParent(node)); + } + + private boolean isParentColour(final SortedQueueEntryImpl node, final SortedQueueEntryImpl.Colour colour) + { + + return node != null && isNodeColour(node.getParent(), colour); + } + + protected boolean isNodeColour(final SortedQueueEntryImpl node, final SortedQueueEntryImpl.Colour colour) + { + return (node == null ? Colour.BLACK : node.getColour()) == colour; + } + + public SortedQueueEntryImpl next(final SortedQueueEntryImpl node) + { + synchronized(_lock) + { + if(node.isDispensed() && _head != node) + { + SortedQueueEntryImpl current = _head; + SortedQueueEntryImpl next; + while(current != null) + { + next = current.getNextValidEntry(); + if(current.compareTo(node)>0 && !current.isDispensed()) + { + break; + } + else + { + current = next; + } + } + return current; + } + else + { + return node.getNextValidEntry(); + } + } + } + + public QueueEntryIterator iterator() + { + return new QueueEntryIteratorImpl(_head); + } + + public SortedQueueEntryImpl getHead() + { + return _head; + } + + protected SortedQueueEntryImpl getRoot() + { + return _root; + } + + public void entryDeleted(final SortedQueueEntryImpl entry) + { + synchronized(_lock) + { + // If the node to be removed has two children, we swap the position + // of the node and its successor in the tree + if(leftChild(entry) != null && rightChild(entry) != null) + { + swapWithSuccessor(entry); + } + + // Then deal with the easy doubly linked list deletion (need to do + // this after the swap as the swap uses next + final SortedQueueEntryImpl prev = entry.getPrev(); + if(prev != null) + { + prev.setNext(entry.getNextValidEntry()); + } + + final SortedQueueEntryImpl next = entry.getNextValidEntry(); + if(next != null) + { + next.setPrev(prev); + } + + // now deal with splicing + final SortedQueueEntryImpl chosenChild; + + if(leftChild(entry) != null) + { + chosenChild = leftChild(entry); + } + else + { + chosenChild = rightChild(entry); + } + + if(chosenChild != null) + { + // we have one child (x), we can move it up to replace x + chosenChild.setParent(entry.getParent()); + if(chosenChild.getParent() == null) + { + _root = chosenChild; + } + else if(entry == entry.getParent().getLeft()) + { + entry.getParent().setLeft(chosenChild); + } + else + { + entry.getParent().setRight(chosenChild); + } + + entry.setLeft(null); + entry.setRight(null); + entry.setParent(null); + + if(entry.getColour() == Colour.BLACK) + { + deleteFixup(chosenChild); + } + + } + else + { + // no children + if(entry.getParent() == null) + { + // no parent either - the tree is empty + _root = null; + } + else + { + if(entry.getColour() == Colour.BLACK) + { + deleteFixup(entry); + } + + if(entry.getParent() != null) + { + if(entry.getParent().getLeft() == entry) + { + entry.getParent().setLeft(null); + } + else if(entry.getParent().getRight() == entry) + { + entry.getParent().setRight(null); + } + entry.setParent(null); + } + } + } + + } + } + + public int getPriorities() + { + return 0; + } + + /** + * Swaps the position of the node in the tree with it's successor + * (that is the node with the next highest key) + * @param entry + */ + private void swapWithSuccessor(final SortedQueueEntryImpl entry) + { + final SortedQueueEntryImpl next = entry.getNextValidEntry(); + final SortedQueueEntryImpl nextParent = next.getParent(); + final SortedQueueEntryImpl nextLeft = next.getLeft(); + final SortedQueueEntryImpl nextRight = next.getRight(); + final Colour nextColour = next.getColour(); + + // Special case - the successor is the right child of the node + if(next == entry.getRight()) + { + next.setParent(entry.getParent()); + if(next.getParent() == null) + { + _root = next; + } + else if(next.getParent().getLeft() == entry) + { + next.getParent().setLeft(next); + } + else + { + next.getParent().setRight(next); + } + + next.setRight(entry); + entry.setParent(next); + next.setLeft(entry.getLeft()); + + if(next.getLeft() != null) + { + next.getLeft().setParent(next); + } + + next.setColour(entry.getColour()); + entry.setColour(nextColour); + entry.setLeft(nextLeft); + + if(nextLeft != null) + { + nextLeft.setParent(entry); + } + entry.setRight(nextRight); + if(nextRight != null) + { + nextRight.setParent(entry); + } + } + else + { + next.setParent(entry.getParent()); + if(next.getParent() == null) + { + _root = next; + } + else if(next.getParent().getLeft() == entry) + { + next.getParent().setLeft(next); + } + else + { + next.getParent().setRight(next); + } + + next.setLeft(entry.getLeft()); + if(next.getLeft() != null) + { + next.getLeft().setParent(next); + } + next.setRight(entry.getRight()); + if(next.getRight() != null) + { + next.getRight().setParent(next); + } + next.setColour(entry.getColour()); + + entry.setParent(nextParent); + if(nextParent.getLeft() == next) + { + nextParent.setLeft(entry); + } + else + { + nextParent.setRight(entry); + } + + entry.setLeft(nextLeft); + if(nextLeft != null) + { + nextLeft.setParent(entry); + } + entry.setRight(nextRight); + if(nextRight != null) + { + nextRight.setParent(entry); + } + entry.setColour(nextColour); + } + } + + private void deleteFixup(SortedQueueEntryImpl entry) + { + int i = 0; + while(entry != null && entry != _root + && isNodeColour(entry, Colour.BLACK)) + { + i++; + + if(i > 1000) + { + return; + } + + if(entry == leftChild(nodeParent(entry))) + { + SortedQueueEntryImpl rightSibling = rightChild(nodeParent(entry)); + if(isNodeColour(rightSibling, Colour.RED)) + { + setColour(rightSibling, Colour.BLACK); + nodeParent(entry).setColour(Colour.RED); + leftRotate(nodeParent(entry)); + rightSibling = rightChild(nodeParent(entry)); + } + + if(isNodeColour(leftChild(rightSibling), Colour.BLACK) + && isNodeColour(rightChild(rightSibling), Colour.BLACK)) + { + setColour(rightSibling, Colour.RED); + entry = nodeParent(entry); + } + else + { + if(isNodeColour(rightChild(rightSibling), Colour.BLACK)) + { + setColour(leftChild(rightSibling), Colour.BLACK); + rightSibling.setColour(Colour.RED); + rightRotate(rightSibling); + rightSibling = rightChild(nodeParent(entry)); + } + setColour(rightSibling, getColour(nodeParent(entry))); + setColour(nodeParent(entry), Colour.BLACK); + setColour(rightChild(rightSibling), Colour.BLACK); + leftRotate(nodeParent(entry)); + entry = _root; + } + } + else + { + SortedQueueEntryImpl leftSibling = leftChild(nodeParent(entry)); + if(isNodeColour(leftSibling, Colour.RED)) + { + setColour(leftSibling, Colour.BLACK); + nodeParent(entry).setColour(Colour.RED); + rightRotate(nodeParent(entry)); + leftSibling = leftChild(nodeParent(entry)); + } + + if(isNodeColour(leftChild(leftSibling), Colour.BLACK) + && isNodeColour(rightChild(leftSibling), Colour.BLACK)) + { + setColour(leftSibling, Colour.RED); + entry = nodeParent(entry); + } + else + { + if(isNodeColour(leftChild(leftSibling), Colour.BLACK)) + { + setColour(rightChild(leftSibling), Colour.BLACK); + leftSibling.setColour(Colour.RED); + leftRotate(leftSibling); + leftSibling = leftChild(nodeParent(entry)); + } + setColour(leftSibling, getColour(nodeParent(entry))); + setColour(nodeParent(entry), Colour.BLACK); + setColour(leftChild(leftSibling), Colour.BLACK); + rightRotate(nodeParent(entry)); + entry = _root; + } + } + } + setColour(entry, Colour.BLACK); + } + + private Colour getColour(final SortedQueueEntryImpl x) + { + return x == null ? null : x.getColour(); + } + + public class QueueEntryIteratorImpl implements QueueEntryIterator + { + private SortedQueueEntryImpl _lastNode; + + public QueueEntryIteratorImpl(final SortedQueueEntryImpl startNode) + { + _lastNode = startNode; + } + + public boolean atTail() + { + return next(_lastNode) == null; + } + + public SortedQueueEntryImpl getNode() + { + return _lastNode; + } + + public boolean advance() + { + if(!atTail()) + { + SortedQueueEntryImpl nextNode = next(_lastNode); + while(nextNode.isDispensed() && next(nextNode) != null) + { + nextNode = next(nextNode); + } + _lastNode = nextNode; + return true; + + } + else + { + return false; + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryListFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryListFactory.java new file mode 100644 index 0000000000..87c79178f0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SortedQueueEntryListFactory.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.queue; + +public class SortedQueueEntryListFactory implements QueueEntryListFactory +{ + + private final String _propertyName; + + public SortedQueueEntryListFactory(final String propertyName) + { + _propertyName = propertyName; + } + + @Override + public QueueEntryList createQueueEntryList(final AMQQueue queue) + { + return new SortedQueueEntryList(queue, _propertyName); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SubFlushRunner.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SubFlushRunner.java new file mode 100755 index 0000000000..47a7d733dd --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/queue/SubFlushRunner.java @@ -0,0 +1,119 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.queue; + + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.transport.TransportException; + +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + + +class SubFlushRunner implements Runnable +{ + private static final Logger _logger = Logger.getLogger(SubFlushRunner.class); + + + private final Subscription _sub; + + private static int IDLE = 0; + private static int SCHEDULED = 1; + private static int RUNNING = 2; + + + private final AtomicInteger _scheduled = new AtomicInteger(IDLE); + + + private static final long ITERATIONS = SimpleAMQQueue.MAX_ASYNC_DELIVERIES; + private final AtomicBoolean _stateChange = new AtomicBoolean(); + + public SubFlushRunner(Subscription sub) + { + _sub = sub; + } + + public void run() + { + if(_scheduled.compareAndSet(SCHEDULED, RUNNING)) + { + boolean complete = false; + _stateChange.set(false); + try + { + CurrentActor.set(_sub.getLogActor()); + complete = getQueue().flushSubscription(_sub, ITERATIONS); + } + catch (AMQException e) + { + _logger.error("Exception during asynchronous delivery by " + toString(), e); + } + catch (final TransportException transe) + { + final String errorMessage = "Problem during asynchronous delivery by " + toString(); + if(_logger.isDebugEnabled()) + { + _logger.debug(errorMessage, transe); + } + else + { + _logger.info(errorMessage + ' ' + transe.getMessage()); + } + } + finally + { + CurrentActor.remove(); + _scheduled.compareAndSet(RUNNING, IDLE); + if ((!complete || _stateChange.compareAndSet(true,false))&& !_sub.isSuspended()) + { + if(_scheduled.compareAndSet(IDLE,SCHEDULED)) + { + getQueue().execute(this); + } + } + } + } + } + + private SimpleAMQQueue getQueue() + { + return (SimpleAMQQueue) _sub.getQueue(); + } + + public String toString() + { + return "SubFlushRunner-" + _sub.getLogActor(); + } + + public void execute(Executor executor) + { + _stateChange.set(true); + if(_scheduled.compareAndSet(IDLE,SCHEDULED)) + { + executor.execute(this); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java new file mode 100644 index 0000000000..9d16f4b927 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java @@ -0,0 +1,355 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.registry; + +import java.util.Collection; +import java.util.Timer; +import java.util.TimerTask; + +import org.apache.log4j.Logger; +import org.apache.qpid.common.Closeable; +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.configuration.startup.DefaultRecovererProvider; +import org.apache.qpid.server.configuration.store.StoreConfigurationChangeListener; +import org.apache.qpid.server.logging.CompositeStartupMessageLogger; +import org.apache.qpid.server.logging.Log4jMessageLogger; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogRecorder; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.SystemOutMessageLogger; +import org.apache.qpid.server.logging.actors.AbstractActor; +import org.apache.qpid.server.logging.actors.BrokerActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.logging.messages.BrokerMessages; +import org.apache.qpid.server.logging.messages.VirtualHostMessages; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.stats.StatisticsCounter; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + + +/** + * An abstract application registry that provides access to configuration information and handles the + * construction and caching of configurable objects. + *

+ * Subclasses should handle the construction of the "registered objects" such as the exchange registry. + */ +public class ApplicationRegistry implements IApplicationRegistry +{ + private static final Logger _logger = Logger.getLogger(ApplicationRegistry.class); + + private final VirtualHostRegistry _virtualHostRegistry = new VirtualHostRegistry(); + + private volatile RootMessageLogger _rootMessageLogger; + + private Broker _broker; + + private Timer _reportingTimer; + private StatisticsCounter _messagesDelivered, _dataDelivered, _messagesReceived, _dataReceived; + + private LogRecorder _logRecorder; + + private ConfigurationEntryStore _store; + private TaskExecutor _taskExecutor; + + protected void setRootMessageLogger(RootMessageLogger rootMessageLogger) + { + _rootMessageLogger = rootMessageLogger; + } + + public ApplicationRegistry(ConfigurationEntryStore store) + { + _store = store; + initialiseStatistics(); + } + + public void initialise(BrokerOptions brokerOptions) throws Exception + { + // Create the RootLogger to be used during broker operation + boolean statusUpdatesEnabled = Boolean.parseBoolean(System.getProperty(BrokerProperties.PROPERTY_STATUS_UPDATES, "true")); + _rootMessageLogger = new Log4jMessageLogger(statusUpdatesEnabled); + + _logRecorder = new LogRecorder(); + + //Create the composite (log4j+SystemOut MessageLogger to be used during startup + RootMessageLogger[] messageLoggers = {new SystemOutMessageLogger(), _rootMessageLogger}; + CompositeStartupMessageLogger startupMessageLogger = new CompositeStartupMessageLogger(messageLoggers); + + BrokerActor actor = new BrokerActor(startupMessageLogger); + CurrentActor.set(actor); + CurrentActor.setDefault(actor); + GenericActor.setDefaultMessageLogger(_rootMessageLogger); + try + { + logStartupMessages(CurrentActor.get()); + + _taskExecutor = new TaskExecutor(); + _taskExecutor.start(); + + StoreConfigurationChangeListener storeChangeListener = new StoreConfigurationChangeListener(_store); + RecovererProvider provider = new DefaultRecovererProvider((StatisticsGatherer)this, _virtualHostRegistry, _logRecorder, _rootMessageLogger, _taskExecutor, brokerOptions, storeChangeListener); + ConfiguredObjectRecoverer brokerRecoverer = provider.getRecoverer(Broker.class.getSimpleName()); + _broker = (Broker) brokerRecoverer.create(provider, _store.getRootEntry()); + + _virtualHostRegistry.setDefaultVirtualHostName((String)_broker.getAttribute(Broker.DEFAULT_VIRTUAL_HOST)); + + initialiseStatisticsReporting(); + + // starting the broker + _broker.setDesiredState(State.INITIALISING, State.ACTIVE); + + CurrentActor.get().message(BrokerMessages.READY()); + } + finally + { + CurrentActor.remove(); + } + + CurrentActor.setDefault(new BrokerActor(_rootMessageLogger)); + } + + private void initialiseStatisticsReporting() + { + long report = ((Number)_broker.getAttribute(Broker.STATISTICS_REPORTING_PERIOD)).intValue() * 1000; // convert to ms + final boolean reset = (Boolean)_broker.getAttribute(Broker.STATISTICS_REPORTING_RESET_ENABLED); + + /* add a timer task to report statistics if generation is enabled for broker or virtualhosts */ + if (report > 0L) + { + _reportingTimer = new Timer("Statistics-Reporting", true); + StatisticsReportingTask task = new StatisticsReportingTask(reset, _rootMessageLogger); + _reportingTimer.scheduleAtFixedRate(task, report / 2, report); + } + } + + private class StatisticsReportingTask extends TimerTask + { + private final int DELIVERED = 0; + private final int RECEIVED = 1; + + private final boolean _reset; + private final RootMessageLogger _logger; + + public StatisticsReportingTask(boolean reset, RootMessageLogger logger) + { + _reset = reset; + _logger = logger; + } + + public void run() + { + CurrentActor.set(new AbstractActor(_logger) + { + public String getLogMessage() + { + return "[" + Thread.currentThread().getName() + "] "; + } + }); + try + { + CurrentActor.get().message(BrokerMessages.STATS_DATA(DELIVERED, _dataDelivered.getPeak() / 1024.0, _dataDelivered.getTotal())); + CurrentActor.get().message(BrokerMessages.STATS_MSGS(DELIVERED, _messagesDelivered.getPeak(), _messagesDelivered.getTotal())); + CurrentActor.get().message(BrokerMessages.STATS_DATA(RECEIVED, _dataReceived.getPeak() / 1024.0, _dataReceived.getTotal())); + CurrentActor.get().message(BrokerMessages.STATS_MSGS(RECEIVED, _messagesReceived.getPeak(), _messagesReceived.getTotal())); + Collection hosts = _virtualHostRegistry.getVirtualHosts(); + + if (hosts.size() > 1) + { + for (VirtualHost vhost : hosts) + { + String name = vhost.getName(); + StatisticsCounter dataDelivered = vhost.getDataDeliveryStatistics(); + StatisticsCounter messagesDelivered = vhost.getMessageDeliveryStatistics(); + StatisticsCounter dataReceived = vhost.getDataReceiptStatistics(); + StatisticsCounter messagesReceived = vhost.getMessageReceiptStatistics(); + + CurrentActor.get().message(VirtualHostMessages.STATS_DATA(name, DELIVERED, dataDelivered.getPeak() / 1024.0, dataDelivered.getTotal())); + CurrentActor.get().message(VirtualHostMessages.STATS_MSGS(name, DELIVERED, messagesDelivered.getPeak(), messagesDelivered.getTotal())); + CurrentActor.get().message(VirtualHostMessages.STATS_DATA(name, RECEIVED, dataReceived.getPeak() / 1024.0, dataReceived.getTotal())); + CurrentActor.get().message(VirtualHostMessages.STATS_MSGS(name, RECEIVED, messagesReceived.getPeak(), messagesReceived.getTotal())); + } + } + + if (_reset) + { + resetStatistics(); + } + } + catch(Exception e) + { + ApplicationRegistry._logger.warn("Unexpected exception occured while reporting the statistics", e); + } + finally + { + CurrentActor.remove(); + } + } + } + + /** + * Close non-null Closeable items and log any errors + * @param close + */ + private void close(Closeable close) + { + try + { + if (close != null) + { + close.close(); + } + } + catch (Throwable e) + { + _logger.error("Error thrown whilst closing " + close.getClass().getSimpleName(), e); + } + } + + public void close() + { + if (_logger.isInfoEnabled()) + { + _logger.info("Shutting down ApplicationRegistry:" + this); + } + + //Set the Actor for Broker Shutdown + CurrentActor.set(new BrokerActor(_rootMessageLogger)); + try + { + //Stop Statistics Reporting + if (_reportingTimer != null) + { + _reportingTimer.cancel(); + } + + if (_broker != null) + { + _broker.setDesiredState(_broker.getActualState(), State.STOPPED); + } + + //Shutdown virtualhosts + close(_virtualHostRegistry); + + if (_taskExecutor != null) + { + _taskExecutor.stop(); + } + + CurrentActor.get().message(BrokerMessages.STOPPED()); + + _logRecorder.closeLogRecorder(); + + } + finally + { + if (_taskExecutor != null) + { + _taskExecutor.stopImmediately(); + } + CurrentActor.remove(); + } + _store = null; + _broker = null; + } + + public void registerMessageDelivered(long messageSize) + { + _messagesDelivered.registerEvent(1L); + _dataDelivered.registerEvent(messageSize); + } + + public void registerMessageReceived(long messageSize, long timestamp) + { + _messagesReceived.registerEvent(1L, timestamp); + _dataReceived.registerEvent(messageSize, timestamp); + } + + public StatisticsCounter getMessageReceiptStatistics() + { + return _messagesReceived; + } + + public StatisticsCounter getDataReceiptStatistics() + { + return _dataReceived; + } + + public StatisticsCounter getMessageDeliveryStatistics() + { + return _messagesDelivered; + } + + public StatisticsCounter getDataDeliveryStatistics() + { + return _dataDelivered; + } + + public void resetStatistics() + { + _messagesDelivered.reset(); + _dataDelivered.reset(); + _messagesReceived.reset(); + _dataReceived.reset(); + + for (VirtualHost vhost : _virtualHostRegistry.getVirtualHosts()) + { + vhost.resetStatistics(); + } + } + + public void initialiseStatistics() + { + _messagesDelivered = new StatisticsCounter("messages-delivered"); + _dataDelivered = new StatisticsCounter("bytes-delivered"); + _messagesReceived = new StatisticsCounter("messages-received"); + _dataReceived = new StatisticsCounter("bytes-received"); + } + + private void logStartupMessages(LogActor logActor) + { + logActor.message(BrokerMessages.STARTUP(QpidProperties.getReleaseVersion(), QpidProperties.getBuildVersion())); + + logActor.message(BrokerMessages.PLATFORM(System.getProperty("java.vendor"), + System.getProperty("java.runtime.version", System.getProperty("java.version")), + System.getProperty("os.name"), + System.getProperty("os.version"), + System.getProperty("os.arch"))); + + logActor.message(BrokerMessages.MAX_MEMORY(Runtime.getRuntime().maxMemory())); + } + + @Override + public Broker getBroker() + { + return _broker; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java new file mode 100644 index 0000000000..7341922bd0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.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.registry; + +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.stats.StatisticsGatherer; + +public interface IApplicationRegistry extends StatisticsGatherer +{ + + void initialise(BrokerOptions brokerOptions) throws Exception; + + /** + * Shutdown this Registry + */ + void close(); + + Broker getBroker(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/AccessControl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/AccessControl.java new file mode 100644 index 0000000000..61e928a38c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/AccessControl.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; + +import org.apache.qpid.server.security.access.ObjectProperties; +import org.apache.qpid.server.security.access.ObjectType; +import org.apache.qpid.server.security.access.Operation; + +/** + * The two methods, {@link #access(ObjectType, Object)} and {@link #authorise(Operation, ObjectType, ObjectProperties)}, + * return the {@link Result} of the security decision, which may be to {@link Result#ABSTAIN} if no decision is made. + */ +public interface AccessControl +{ + /** + * Default result for {@link #access(ObjectType, Object)} or {@link #authorise(Operation, ObjectType, ObjectProperties)}. + */ + Result getDefault(); + + /** + * Authorise access granted to an object instance. + */ + Result access(ObjectType objectType, Object instance); + + /** + * Authorise an operation on an object defined by a set of properties. + */ + Result authorise(Operation operation, ObjectType objectType, ObjectProperties properties); + + /** + * Called to open any resources required by the implementation. + */ + void open(); + + /** + * Called to close any resources required by the implementation. + */ + void close(); + + /** + * Called when deleting to allow clearing any resources used by the implementation. + */ + void onDelete(); + + /** + * Called when first creating (but not when recovering after startup) to allow + * creating any resources required by the implementation. + */ + void onCreate(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/AuthorizationHolder.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/AuthorizationHolder.java new file mode 100755 index 0000000000..8243fc3f75 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/AuthorizationHolder.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security; + +import javax.security.auth.Subject; +import java.security.Principal; + +/** + * Represents the authorization of the logged on user. + * + */ +public interface AuthorizationHolder +{ + /** + * Returns the {@link Subject} of the authorized user. This is guaranteed to + * contain at least one {@link org.apache.qpid.server.security.auth.UsernamePrincipal}, representing the the identity + * used when the user logged on to the application, and zero or more {@link org.apache.qpid.server.security.auth.sasl.GroupPrincipal} + * representing the group(s) to which the user belongs. + * + * @return the Subject + */ + Subject getAuthorizedSubject(); + + /** + * Returns the {@link Principal} representing the the identity + * used when the user logged on to the application. + * + * @return a Principal + */ + Principal getAuthorizedPrincipal(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/Result.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/Result.java new file mode 100644 index 0000000000..f79721799e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/Result.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.security; + +/** + * The result of a security plugin decision, normally {@link #ALLOWED} or {@link #DENIED}. + */ +public enum Result +{ + /** + * The request is allowed. + */ + ALLOWED, + + /** + * The request is denied. + */ + DENIED, + + /** + * Indicates that a plugin does not handle a particular type of request. + */ + ABSTAIN, + + /** + * A plugin instance cannot make a decision on a request it is able to handle, + * and another instance of the plugin should be checked. + */ + DEFER; +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SecurityManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SecurityManager.java new file mode 100755 index 0000000000..ff45add206 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SecurityManager.java @@ -0,0 +1,643 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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; + +import org.apache.log4j.Logger; + +import org.apache.qpid.server.exchange.Exchange; + +import org.apache.qpid.server.model.AccessControlProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfigurationChangeListener; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.plugin.AccessControlFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.security.access.FileAccessControlProviderConstants; +import org.apache.qpid.server.security.access.ObjectProperties; +import org.apache.qpid.server.security.access.ObjectType; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.security.access.OperationLoggingDetails; + +import static org.apache.qpid.server.security.access.ObjectType.BROKER; +import static org.apache.qpid.server.security.access.ObjectType.EXCHANGE; +import static org.apache.qpid.server.security.access.ObjectType.GROUP; +import static org.apache.qpid.server.security.access.ObjectType.METHOD; +import static org.apache.qpid.server.security.access.ObjectType.QUEUE; +import static org.apache.qpid.server.security.access.ObjectType.USER; +import static org.apache.qpid.server.security.access.ObjectType.VIRTUALHOST; +import static org.apache.qpid.server.security.access.Operation.ACCESS_LOGS; +import static org.apache.qpid.server.security.access.Operation.BIND; +import static org.apache.qpid.server.security.access.Operation.CONFIGURE; +import static org.apache.qpid.server.security.access.Operation.CONSUME; +import static org.apache.qpid.server.security.access.Operation.CREATE; +import static org.apache.qpid.server.security.access.Operation.DELETE; +import static org.apache.qpid.server.security.access.Operation.PUBLISH; +import static org.apache.qpid.server.security.access.Operation.PURGE; +import static org.apache.qpid.server.security.access.Operation.UNBIND; +import static org.apache.qpid.server.security.access.Operation.UPDATE; + +import javax.security.auth.Subject; +import java.net.SocketAddress; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +public class SecurityManager implements ConfigurationChangeListener +{ + private static final Logger _logger = Logger.getLogger(SecurityManager.class); + + /** Container for the {@link java.security.Principal} that is using to this thread. */ + private static final ThreadLocal _subject = new ThreadLocal(); + + public static final ThreadLocal _accessChecksDisabled = new ClearingThreadLocal(false); + + private ConcurrentHashMap _globalPlugins = new ConcurrentHashMap(); + private ConcurrentHashMap _hostPlugins = new ConcurrentHashMap(); + + private boolean _managementMode; + + private Broker _broker; + + /** + * A special ThreadLocal, which calls remove() on itself whenever the value is + * the default, to avoid leaving a default value set after its use has passed. + */ + private static final class ClearingThreadLocal extends ThreadLocal + { + private Boolean _defaultValue; + + public ClearingThreadLocal(Boolean defaultValue) + { + super(); + _defaultValue = defaultValue; + } + + @Override + protected Boolean initialValue() + { + return _defaultValue; + } + + @Override + public void set(Boolean value) + { + if (value == _defaultValue) + { + super.remove(); + } + else + { + super.set(value); + } + } + + @Override + public Boolean get() + { + Boolean value = super.get(); + if (value == _defaultValue) + { + super.remove(); + } + return value; + } + } + + /* + * Used by the Broker. + */ + public SecurityManager(Broker broker, boolean managementMode) + { + _managementMode = managementMode; + _broker = broker; + } + + /* + * Used by the VirtualHost to allow deferring to the broker level security plugins if required. + */ + public SecurityManager(SecurityManager parent, String aclFile, String vhostName) + { + _managementMode = parent._managementMode; + + if(!_managementMode) + { + configureVirtualHostAclPlugin(aclFile, vhostName); + + // our global plugins are the parent's host plugins + _globalPlugins = parent._hostPlugins; + } + } + + private void configureVirtualHostAclPlugin(String aclFile, String vhostName) + { + if(aclFile != null) + { + Map attributes = new HashMap(); + + attributes.put(AccessControlProvider.TYPE, FileAccessControlProviderConstants.ACL_FILE_PROVIDER_TYPE); + attributes.put(FileAccessControlProviderConstants.PATH, aclFile); + + for (AccessControlFactory provider : (new QpidServiceLoader()).instancesOf(AccessControlFactory.class)) + { + AccessControl accessControl = provider.createInstance(attributes); + accessControl.open(); + if(accessControl != null) + { + String pluginTypeName = getPluginTypeName(accessControl); + _hostPlugins.put(pluginTypeName, accessControl); + + if(_logger.isDebugEnabled()) + { + _logger.debug("Added access control to host plugins with name: " + vhostName); + } + + break; + } + } + } + + if(_logger.isDebugEnabled()) + { + _logger.debug("Configured " + _hostPlugins.size() + " access control plugins"); + } + } + + private String getPluginTypeName(AccessControl accessControl) + { + return accessControl.getClass().getName(); + } + + public static Subject getThreadSubject() + { + return _subject.get(); + } + + public static void setThreadSubject(final Subject subject) + { + _subject.set(subject); + } + + public static Logger getLogger() + { + return _logger; + } + + private abstract class AccessCheck + { + abstract Result allowed(AccessControl plugin); + } + + private boolean checkAllPlugins(AccessCheck checker) + { + if(_accessChecksDisabled.get()) + { + return true; + } + + Map remainingPlugins = _globalPlugins.isEmpty() + ? Collections.emptyMap() + : _hostPlugins.isEmpty() ? _globalPlugins : new HashMap(_globalPlugins); + + if(!_hostPlugins.isEmpty()) + { + for (Entry hostEntry : _hostPlugins.entrySet()) + { + // Create set of global only plugins + AccessControl globalPlugin = remainingPlugins.get(hostEntry.getKey()); + if (globalPlugin != null) + { + remainingPlugins.remove(hostEntry.getKey()); + } + + Result host = checker.allowed(hostEntry.getValue()); + + if (host == Result.DENIED) + { + // Something vetoed the access, we're done + return false; + } + + // host allow overrides global allow, so only check global on abstain or defer + if (host != Result.ALLOWED) + { + if (globalPlugin == null) + { + if (host == Result.DEFER) + { + host = hostEntry.getValue().getDefault(); + } + if (host == Result.DENIED) + { + return false; + } + } + else + { + Result global = checker.allowed(globalPlugin); + if (global == Result.DEFER) + { + global = globalPlugin.getDefault(); + } + if (global == Result.ABSTAIN && host == Result.DEFER) + { + global = hostEntry.getValue().getDefault(); + } + if (global == Result.DENIED) + { + return false; + } + } + } + } + } + + for (AccessControl plugin : remainingPlugins.values()) + { + Result remaining = checker.allowed(plugin); + if (remaining == Result.DEFER) + { + remaining = plugin.getDefault(); + } + if (remaining == Result.DENIED) + { + return false; + } + } + + // getting here means either allowed or abstained from all plugins + return true; + } + + public boolean authoriseBind(final Exchange exch, final AMQQueue queue, final String routingKey) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(BIND, EXCHANGE, new ObjectProperties(exch, queue, routingKey)); + } + }); + } + + public boolean authoriseMethod(final Operation operation, final String componentName, final String methodName) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + ObjectProperties properties = new ObjectProperties(); + properties.setName(methodName); + if (componentName != null) + { + // Only set the property if there is a component name + properties.put(ObjectProperties.Property.COMPONENT, componentName); + } + return plugin.authorise(operation, METHOD, properties); + } + }); + } + + public boolean accessManagement() + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.access(ObjectType.MANAGEMENT, null); + } + }); + } + + public boolean accessVirtualhost(final String vhostname, final SocketAddress remoteAddress) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.access(VIRTUALHOST, remoteAddress); + } + }); + } + + public boolean authoriseConsume(final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(CONSUME, QUEUE, new ObjectProperties(queue)); + } + }); + } + + public boolean authoriseCreateExchange(final Boolean autoDelete, final Boolean durable, final String exchangeName, + final Boolean internal, final Boolean nowait, final Boolean passive, final String exchangeType) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(CREATE, EXCHANGE, new ObjectProperties(autoDelete, durable, exchangeName, + internal, nowait, passive, exchangeType)); + } + }); + } + + public boolean authoriseCreateQueue(final Boolean autoDelete, final Boolean durable, final Boolean exclusive, + final Boolean nowait, final Boolean passive, final String queueName, final String owner) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(CREATE, QUEUE, new ObjectProperties(autoDelete, durable, exclusive, nowait, passive, queueName, owner)); + } + }); + } + + public boolean authoriseDelete(final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(DELETE, QUEUE, new ObjectProperties(queue)); + } + }); + } + + + public boolean authoriseUpdate(final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(UPDATE, QUEUE, new ObjectProperties(queue)); + } + }); + } + + + public boolean authoriseUpdate(final Exchange exchange) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(UPDATE, EXCHANGE, new ObjectProperties(exchange.getName())); + } + }); + } + + public boolean authoriseDelete(final Exchange exchange) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(DELETE, EXCHANGE, new ObjectProperties(exchange.getName())); + } + }); + } + + public boolean authoriseGroupOperation(final Operation operation, final String groupName) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(operation, GROUP, new ObjectProperties(groupName)); + } + }); + } + + public boolean authoriseUserOperation(final Operation operation, final String userName) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(operation, USER, new ObjectProperties(userName)); + } + }); + } + + private ConcurrentHashMap> _immediatePublishPropsCache + = new ConcurrentHashMap>(); + private ConcurrentHashMap> _publishPropsCache + = new ConcurrentHashMap>(); + + public boolean authorisePublish(final boolean immediate, String routingKey, String exchangeName) + { + if(routingKey == null) + { + routingKey = ""; + } + if(exchangeName == null) + { + exchangeName = ""; + } + PublishAccessCheck check; + ConcurrentHashMap> cache = + immediate ? _immediatePublishPropsCache : _publishPropsCache; + + ConcurrentHashMap exchangeMap = cache.get(exchangeName); + if(exchangeMap == null) + { + cache.putIfAbsent(exchangeName, new ConcurrentHashMap()); + exchangeMap = cache.get(exchangeName); + } + + check = exchangeMap.get(routingKey); + if(check == null) + { + check = new PublishAccessCheck(new ObjectProperties(exchangeName, routingKey, immediate)); + exchangeMap.put(routingKey, check); + } + + return checkAllPlugins(check); + } + + public boolean authorisePurge(final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(PURGE, QUEUE, new ObjectProperties(queue)); + } + }); + } + + public boolean authoriseUnbind(final Exchange exch, final String routingKey, final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(UNBIND, EXCHANGE, new ObjectProperties(exch, queue, routingKey)); + } + }); + } + + public static boolean setAccessChecksDisabled(final boolean status) + { + //remember current value + boolean current = _accessChecksDisabled.get(); + + _accessChecksDisabled.set(status); + + return current; + } + + private class PublishAccessCheck extends AccessCheck + { + private final ObjectProperties _props; + + public PublishAccessCheck(ObjectProperties props) + { + _props = props; + } + + Result allowed(AccessControl plugin) + { + return plugin.authorise(PUBLISH, EXCHANGE, _props); + } + } + + @Override + public void stateChanged(ConfiguredObject object, State oldState, State newState) + { + if(_managementMode) + { + //AccessControl is disabled in ManagementMode + return; + } + + if(object instanceof AccessControlProvider) + { + if(newState == State.ACTIVE) + { + synchronized (_hostPlugins) + { + AccessControl accessControl = ((AccessControlProvider)object).getAccessControl(); + String pluginTypeName = getPluginTypeName(accessControl); + + _hostPlugins.put(pluginTypeName, accessControl); + } + } + else if(newState == State.DELETED) + { + synchronized (_hostPlugins) + { + AccessControl control = ((AccessControlProvider)object).getAccessControl(); + String pluginTypeName = getPluginTypeName(control); + + // Remove the type->control mapping for this type key only if the + // given control is actually referred to. + if(_hostPlugins.containsValue(control)) + { + // If we are removing this control, check if another of the same + // type already exists on the broker and use it in instead. + AccessControl other = null; + Collection providers = _broker.getAccessControlProviders(); + for(AccessControlProvider p : providers) + { + if(p == object || p.getActualState() != State.ACTIVE) + { + //we don't count ourself as another + continue; + } + + AccessControl ac = p.getAccessControl(); + if(pluginTypeName.equals(getPluginTypeName(ac))) + { + other = ac; + break; + } + } + + if(other != null) + { + //Another control of this type was found, use it instead + _hostPlugins.replace(pluginTypeName, control, other); + } + else + { + //No other was found, remove the type entirely + _hostPlugins.remove(pluginTypeName); + } + } + } + } + } + } + + @Override + public void childAdded(ConfiguredObject object, ConfiguredObject child) + { + // no op + } + + @Override + public void childRemoved(ConfiguredObject object, ConfiguredObject child) + { + // no op + } + + @Override + public void attributeSet(ConfiguredObject object, String attributeName, Object oldAttributeValue, Object newAttributeValue) + { + // no op + } + + public boolean authoriseConfiguringBroker(String configuredObjectName, Class configuredObjectType, Operation configuredObjectOperation) + { + String description = String.format("%s %s '%s'", + configuredObjectOperation == null? null : configuredObjectOperation.name().toLowerCase(), + configuredObjectType == null ? null : configuredObjectType.getSimpleName().toLowerCase(), + configuredObjectName); + final OperationLoggingDetails properties = new OperationLoggingDetails(description); + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(CONFIGURE, BROKER, properties); + } + }); + } + + public boolean authoriseLogsAccess() + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(AccessControl plugin) + { + return plugin.authorise(ACCESS_LOGS, BROKER, ObjectProperties.EMPTY); + } + }); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SubjectCreator.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SubjectCreator.java new file mode 100644 index 0000000000..244ab0dd94 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/SubjectCreator.java @@ -0,0 +1,162 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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; + +import java.security.Principal; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; +import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManager; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; + +/** + * Creates a {@link Subject} formed by the {@link Principal}'s returned from: + *

    + *
  1. Authenticating using an {@link AuthenticationManager}
  2. + *
  3. A {@link GroupPrincipalAccessor}
  4. + *
+ * + *

+ * SubjectCreator is a facade to the {@link AuthenticationManager}, and is intended to be + * the single place that {@link Subject}'s are created in the broker. + *

+ */ +public class SubjectCreator +{ + private AuthenticationManager _authenticationManager; + private Collection _groupProviders; + + public SubjectCreator(AuthenticationManager authenticationManager, Collection groupProviders) + { + _authenticationManager = authenticationManager; + _groupProviders = groupProviders; + } + + /** + * Gets the known SASL mechanisms + * + * @return SASL mechanism names, space separated. + */ + public String getMechanisms() + { + return _authenticationManager.getMechanisms(); + } + + /** + * @see AuthenticationManager#createSaslServer(String, String, Principal) + */ + public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException + { + return _authenticationManager.createSaslServer(mechanism, localFQDN, externalPrincipal); + } + + /** + * Authenticates a user using SASL negotiation. + * + * @param server SASL server + * @param response SASL response to process + */ + public SubjectAuthenticationResult authenticate(SaslServer server, byte[] response) + { + AuthenticationResult authenticationResult = _authenticationManager.authenticate(server, response); + if(server.isComplete()) + { + String username = server.getAuthorizationID(); + + return createResultWithGroups(username, authenticationResult); + } + else + { + return new SubjectAuthenticationResult(authenticationResult); + } + } + + /** + * Authenticates a user using their username and password. + */ + public SubjectAuthenticationResult authenticate(String username, String password) + { + final AuthenticationResult authenticationResult = _authenticationManager.authenticate(username, password); + + return createResultWithGroups(username, authenticationResult); + } + + private SubjectAuthenticationResult createResultWithGroups(String username, final AuthenticationResult authenticationResult) + { + if(authenticationResult.getStatus() == AuthenticationStatus.SUCCESS) + { + final Subject authenticationSubject = new Subject(); + + authenticationSubject.getPrincipals().addAll(authenticationResult.getPrincipals()); + authenticationSubject.getPrincipals().addAll(getGroupPrincipals(username)); + + authenticationSubject.setReadOnly(); + + return new SubjectAuthenticationResult(authenticationResult, authenticationSubject); + } + else + { + return new SubjectAuthenticationResult(authenticationResult); + } + } + + public Subject createSubjectWithGroups(String username) + { + Subject authenticationSubject = new Subject(); + + authenticationSubject.getPrincipals().add(new AuthenticatedPrincipal(username)); + authenticationSubject.getPrincipals().addAll(getGroupPrincipals(username)); + authenticationSubject.setReadOnly(); + + return authenticationSubject; + } + + public Set getGroupPrincipals(String username) + { + Set principals = new HashSet(); + for (GroupProvider groupProvider : _groupProviders) + { + Set groups = groupProvider.getGroupPrincipalsForUser(username); + if (groups != null) + { + principals.addAll(groups); + } + } + + return Collections.unmodifiableSet(principals); + } + + public boolean isAnonymousAuthenticationAllowed() + { + return _authenticationManager instanceof AnonymousAuthenticationManager; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/FileAccessControlProviderConstants.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/FileAccessControlProviderConstants.java new file mode 100644 index 0000000000..3a98a947df --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/FileAccessControlProviderConstants.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 class FileAccessControlProviderConstants +{ + public static final String ACL_FILE_PROVIDER_TYPE = "AclFile"; + public static final String PATH = "path"; +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java new file mode 100644 index 0000000000..a379f85bbb --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java @@ -0,0 +1,336 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.security.access; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; + +/** + * An set of properties for an access control v2 rule {@link ObjectType}. + * + * The {@link #matches(ObjectProperties)} method is intended to be used when determining precedence of rules, and + * {@link #equals(Object)} and {@link #hashCode()} are intended for use in maps. This is due to the wildcard matching + * described above. + */ +public class ObjectProperties +{ + public static final String STAR= "*"; + + public static final ObjectProperties EMPTY = new ObjectProperties(); + + public enum Property + { + ROUTING_KEY, + NAME, + QUEUE_NAME, + OWNER, + TYPE, + ALTERNATE, + IMMEDIATE, + INTERNAL, + NO_WAIT, + NO_LOCAL, + NO_ACK, + PASSIVE, + DURABLE, + EXCLUSIVE, + TEMPORARY, + AUTO_DELETE, + COMPONENT, + PACKAGE, + CLASS, + FROM_NETWORK, + FROM_HOSTNAME; + + private static final Map _canonicalNameToPropertyMap = new HashMap(); + + static + { + for (Property property : values()) + { + _canonicalNameToPropertyMap.put(getCanonicalName(property.name()), property); + } + } + + /** + * Properties are parsed using their canonical name (see {@link #getCanonicalName(String)}) + * so that, for the sake of user-friendliness, the ACL file parses is insensitive to + * case and underscores. + */ + public static Property parse(String text) + { + String propertyName = getCanonicalName(text); + Property property = _canonicalNameToPropertyMap.get(propertyName); + + if(property == null) + { + throw new IllegalArgumentException("Not a valid property: " + text + + " because " + propertyName + + " is not in " + _canonicalNameToPropertyMap.keySet()); + } + else + { + return property; + } + } + + private static String getCanonicalName(String name) + { + return StringUtils.remove(name, '_').toLowerCase(); + } + } + + private final EnumMap _properties = new EnumMap(Property.class); + + public static List getAllPropertyNames() + { + List properties = new ArrayList(); + for (Property property : Property.values()) + { + properties.add(StringUtils.remove(property.name(), '_').toLowerCase()); + } + return properties; + } + + public ObjectProperties() + { + } + + public ObjectProperties(Property property, String value) + { + _properties.put(property, value); + } + + public ObjectProperties(ObjectProperties copy) + { + _properties.putAll(copy._properties); + } + + public ObjectProperties(String name) + { + setName(name); + } + + public ObjectProperties(AMQQueue queue) + { + setName(queue.getName()); + + put(Property.AUTO_DELETE, queue.isAutoDelete()); + put(Property.TEMPORARY, queue.isAutoDelete()); + put(Property.DURABLE, queue.isDurable()); + put(Property.EXCLUSIVE, queue.isExclusive()); + if (queue.getAlternateExchange() != null) + { + put(Property.ALTERNATE, queue.getAlternateExchange().getName()); + } + if (queue.getOwner() != null) + { + put(Property.OWNER, queue.getOwner()); + } + else if (queue.getAuthorizationHolder() != null) + { + put(Property.OWNER, queue.getAuthorizationHolder().getAuthorizedPrincipal().getName()); + } + } + + public ObjectProperties(Exchange exch, AMQQueue queue, String routingKey) + { + this(queue); + + setName(exch.getName()); + + put(Property.QUEUE_NAME, queue.getName()); + put(Property.ROUTING_KEY, routingKey); + } + + public ObjectProperties(String exchangeName, String routingKey, Boolean immediate) + { + this(exchangeName, routingKey); + + put(Property.IMMEDIATE, immediate); + } + + public ObjectProperties(String exchangeName, String routingKey) + { + super(); + + setName(exchangeName); + + put(Property.ROUTING_KEY, routingKey); + } + + public ObjectProperties(Boolean autoDelete, Boolean durable, String exchangeName, + Boolean internal, Boolean nowait, Boolean passive, String exchangeType) + { + super(); + + setName(exchangeName); + + put(Property.AUTO_DELETE, autoDelete); + put(Property.TEMPORARY, autoDelete); + put(Property.DURABLE, durable); + put(Property.INTERNAL, internal); + put(Property.NO_WAIT, nowait); + put(Property.PASSIVE, passive); + put(Property.TYPE, exchangeType); + } + + public ObjectProperties(Boolean autoDelete, Boolean durable, Boolean exclusive, Boolean nowait, Boolean passive, + String queueName, String owner) + { + super(); + + setName(queueName); + + put(Property.AUTO_DELETE, autoDelete); + put(Property.TEMPORARY, autoDelete); + put(Property.DURABLE, durable); + put(Property.EXCLUSIVE, exclusive); + put(Property.NO_WAIT, nowait); + put(Property.PASSIVE, passive); + put(Property.OWNER, owner); + } + + public ObjectProperties(Boolean exclusive, Boolean noAck, Boolean noLocal, Boolean nowait, AMQQueue queue) + { + this(queue); + + put(Property.NO_LOCAL, noLocal); + put(Property.NO_ACK, noAck); + put(Property.EXCLUSIVE, exclusive); + put(Property.NO_WAIT, nowait); + } + + public Boolean isSet(Property key) + { + return _properties.containsKey(key) && Boolean.valueOf(_properties.get(key)); + } + + public String get(Property key) + { + return _properties.get(key); + } + + public String getName() + { + return _properties.get(Property.NAME); + } + + public void setName(String name) + { + _properties.put(Property.NAME, name); + } + + public String put(Property key, String value) + { + return _properties.put(key, value == null ? "" : value.trim()); + } + + public void put(Property key, Boolean value) + { + if (value != null) + { + _properties.put(key, Boolean.toString(value)); + } + } + + public boolean matches(ObjectProperties properties) + { + if (properties._properties.keySet().isEmpty()) + { + return true; + } + + if (!_properties.keySet().containsAll(properties._properties.keySet())) + { + return false; + } + + for (Map.Entry entry : properties._properties.entrySet()) + { + Property key = entry.getKey(); + String ruleValue = entry.getValue(); + + String thisValue = _properties.get(key); + + if (!valueMatches(thisValue, ruleValue)) + { + return false; + } + } + + return true; + } + + private boolean valueMatches(String thisValue, String ruleValue) + { + return (StringUtils.isEmpty(ruleValue) + || StringUtils.equals(thisValue, ruleValue)) + || ruleValue.equals(STAR) + || (ruleValue.endsWith(STAR) + && thisValue != null + && thisValue.length() >= ruleValue.length() - 1 + && thisValue.startsWith(ruleValue.substring(0, ruleValue.length() - 1))); + } + + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + if (obj == this) + { + return true; + } + if (obj.getClass() != getClass()) + { + return false; + } + ObjectProperties rhs = (ObjectProperties) obj; + return new EqualsBuilder() + .append(_properties, rhs._properties).isEquals(); + } + + @Override + public int hashCode() + { + return _properties != null ? _properties.hashCode() : 0; + } + + @Override + public String toString() + { + return _properties.toString(); + } + + public boolean isEmpty() + { + return _properties.isEmpty(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/ObjectType.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/ObjectType.java new file mode 100644 index 0000000000..9016205d1c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/ObjectType.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 static org.apache.qpid.server.security.access.Operation.ACCESS; +import static org.apache.qpid.server.security.access.Operation.ACCESS_LOGS; +import static org.apache.qpid.server.security.access.Operation.BIND; +import static org.apache.qpid.server.security.access.Operation.CONFIGURE; +import static org.apache.qpid.server.security.access.Operation.CONSUME; +import static org.apache.qpid.server.security.access.Operation.CREATE; +import static org.apache.qpid.server.security.access.Operation.DELETE; +import static org.apache.qpid.server.security.access.Operation.PUBLISH; +import static org.apache.qpid.server.security.access.Operation.PURGE; +import static org.apache.qpid.server.security.access.Operation.UNBIND; +import static org.apache.qpid.server.security.access.Operation.UPDATE; + +import java.util.EnumSet; +import java.util.Set; + +/** + * An enumeration of all possible object types that can form part of an access control v2 rule. + * + * Each object type is valid only for a certain set of {@link Operation}s, which are passed as a list to + * the constructor, and can be checked using the {@link #isAllowed(Operation)} method. + */ +public enum ObjectType +{ + ALL(Operation.ALL), + VIRTUALHOST(Operation.ALL, ACCESS), + MANAGEMENT(Operation.ALL, ACCESS), + QUEUE(Operation.ALL, CREATE, DELETE, PURGE, CONSUME, UPDATE), + EXCHANGE(Operation.ALL, ACCESS, CREATE, DELETE, BIND, UNBIND, PUBLISH, UPDATE), + LINK, // Not allowed in the Java broker + ROUTE, // Not allowed in the Java broker + METHOD(Operation.ALL, ACCESS, UPDATE), + USER(Operation.ALL, CREATE, DELETE, UPDATE), + GROUP(Operation.ALL, CREATE, DELETE, UPDATE), + BROKER(Operation.ALL, CONFIGURE, ACCESS_LOGS); + + private EnumSet _actions; + + private ObjectType() + { + _actions = EnumSet.noneOf(Operation.class); + } + + private ObjectType(Operation operation) + { + if (operation == Operation.ALL) + { + _actions = EnumSet.allOf(Operation.class); + } + else + { + _actions = EnumSet.of(operation); + } + } + + private ObjectType(Operation first, Operation...rest) + { + _actions = EnumSet.of(first, rest); + } + + public Set getActions() + { + return _actions; + } + + public boolean isAllowed(Operation operation) + { + return _actions.contains(operation); + } + + public static ObjectType parse(String text) + { + for (ObjectType object : values()) + { + if (object.name().equalsIgnoreCase(text)) + { + return object; + } + } + throw new IllegalArgumentException("Not a valid object type: " + text); + } + + public String toString() + { + String name = name(); + return name.charAt(0) + name.substring(1).toLowerCase(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/Operation.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/Operation.java new file mode 100644 index 0000000000..db5b8fba11 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/Operation.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.security.access; + +/** + * An enumeration of all possible actions that can form part of an access control v2 rule. + */ +public enum Operation +{ + ALL, + CONSUME, + PUBLISH, + CREATE, + ACCESS, + BIND, + UNBIND, + DELETE, + PURGE, + UPDATE, + CONFIGURE, + ACCESS_LOGS; + + public static Operation parse(String text) + { + for (Operation operation : values()) + { + if (operation.name().equalsIgnoreCase(text)) + { + return operation; + } + } + throw new IllegalArgumentException("Not a valid operation: " + text); + } + + public String toString() + { + String name = name(); + return name.charAt(0) + name.substring(1).toLowerCase(); + } +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/OperationLoggingDetails.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/OperationLoggingDetails.java new file mode 100644 index 0000000000..a683199abc --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/OperationLoggingDetails.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.security.access; + + +public class OperationLoggingDetails extends ObjectProperties +{ + private String _description; + + public OperationLoggingDetails(String description) + { + super(); + _description = description; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder("("); + if (!super.isEmpty()) + { + sb.append("properties=").append(super.toString()); + } + if (_description != null) + { + if (sb.length() > 1) + { + sb.append(", "); + } + sb.append(_description); + } + sb.append(")"); + return sb.toString(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/Permission.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/Permission.java new file mode 100644 index 0000000000..49b3a331f9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/access/Permission.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.access; + +import org.apache.commons.lang.StringUtils; + +/** + * An enumeration of all possible permissions that can be applied to an access control v2 rule. + */ +public enum Permission +{ + ALLOW, + ALLOW_LOG, + DENY, + DENY_LOG; + + public static Permission parse(String text) + { + + for (Permission permission : values()) + { + if (permission.name().equalsIgnoreCase(StringUtils.replaceChars(text, '-', '_'))) + { + return permission; + } + } + throw new IllegalArgumentException("Not a valid permission: " + text); + } +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipal.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipal.java new file mode 100644 index 0000000000..fb31132514 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipal.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.security.auth; + +import java.io.Serializable; +import java.security.Principal; +import java.util.Set; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.UsernamePrincipal; + +/** + * A simple Principal wrapper. Exists to allow us to identify the "primary" principal + * by calling {@link Subject#getPrincipals(Class)}, passing in {@link AuthenticatedPrincipal}.class, + * e.g. when logging. + */ +public final class AuthenticatedPrincipal implements Principal, Serializable +{ + private final Principal _wrappedPrincipal; + + /** convenience constructor for the common case where we're wrapping a {@link UsernamePrincipal} */ + public AuthenticatedPrincipal(String userPrincipalName) + { + this(new UsernamePrincipal(userPrincipalName)); + } + + public AuthenticatedPrincipal(Principal wrappedPrincipal) + { + if(wrappedPrincipal == null) + { + throw new IllegalArgumentException("Wrapped principal is null"); + } + + _wrappedPrincipal = wrappedPrincipal; + } + + @Override + public String getName() + { + return _wrappedPrincipal.getName(); + } + + @Override + public int hashCode() + { + return _wrappedPrincipal.hashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + + if (!(obj instanceof AuthenticatedPrincipal)) + { + return false; + } + + AuthenticatedPrincipal other = (AuthenticatedPrincipal) obj; + + return _wrappedPrincipal.equals(other._wrappedPrincipal); + } + + public static AuthenticatedPrincipal getOptionalAuthenticatedPrincipalFromSubject(final Subject authSubject) + { + return getAuthenticatedPrincipalFromSubject(authSubject, true); + } + + public static AuthenticatedPrincipal getAuthenticatedPrincipalFromSubject(final Subject authSubject) + { + return getAuthenticatedPrincipalFromSubject(authSubject, false); + } + + private static AuthenticatedPrincipal getAuthenticatedPrincipalFromSubject(final Subject authSubject, boolean isPrincipalOptional) + { + if (authSubject == null) + { + throw new IllegalArgumentException("No authenticated subject."); + } + + final Set principals = authSubject.getPrincipals(AuthenticatedPrincipal.class); + int numberOfAuthenticatedPrincipals = principals.size(); + + if(numberOfAuthenticatedPrincipals == 0 && isPrincipalOptional) + { + return null; + } + else + { + if (numberOfAuthenticatedPrincipals != 1) + { + throw new IllegalArgumentException( + "Can't find single AuthenticatedPrincipal in authenticated subject. There were " + + numberOfAuthenticatedPrincipals + + " authenticated principals out of a total number of principals of: " + authSubject.getPrincipals()); + } + return principals.iterator().next(); + } + } + + @Override + public String toString() + { + return getName(); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java new file mode 100644 index 0000000000..09bf6cf3b1 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.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.security.auth; + +import java.security.Principal; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; + +/** + * Encapsulates the result of an attempt to authenticate using an {@link AuthenticationManager}. + *

+ * The authentication status describes the overall outcome. + *

+ *

    + *
  1. If authentication status is SUCCESS, at least one {@link Principal} will be populated. + *
  2. + *
  3. If authentication status is CONTINUE, the authentication has failed because the user + * supplied incorrect credentials (etc). If the authentication requires it, the next challenge + * is made available. + *
  4. + *
  5. If authentication status is ERROR , the authentication decision could not be made due + * to a failure (such as an external system), the {@link AuthenticationResult#getCause()} + * will provide the underlying exception. + *
  6. + *
+ * + * The main principal provided to the constructor is wrapped in an {@link AuthenticatedPrincipal} + * to make it easier for the rest of the application to identify it among the set of other principals. + */ +public class AuthenticationResult +{ + public enum AuthenticationStatus + { + /** Authentication successful */ + SUCCESS, + /** Authentication not successful due to credentials problem etc */ + CONTINUE, + /** Problem prevented the authentication from being made e.g. failure of an external system */ + ERROR + } + + private final AuthenticationStatus _status; + private final byte[] _challenge; + private final Exception _cause; + private final Set _principals = new HashSet(); + private final Principal _mainPrincipal; + + public AuthenticationResult(final AuthenticationStatus status) + { + this(null, status, null); + } + + public AuthenticationResult(Principal mainPrincipal) + { + this(mainPrincipal, Collections.emptySet()); + } + + public AuthenticationResult(Principal mainPrincipal, Set otherPrincipals) + { + AuthenticatedPrincipal specialQpidAuthenticatedPrincipal = new AuthenticatedPrincipal(mainPrincipal); + _principals.addAll(otherPrincipals); + _principals.remove(mainPrincipal); + _principals.add(specialQpidAuthenticatedPrincipal); + _mainPrincipal = mainPrincipal; + + _status = AuthenticationStatus.SUCCESS; + _challenge = null; + _cause = null; + } + + public AuthenticationResult(final byte[] challenge, final AuthenticationStatus status) + { + _challenge = challenge; + _status = status; + _cause = null; + _mainPrincipal = null; + } + + public AuthenticationResult(final AuthenticationStatus error, final Exception cause) + { + _status = error; + _challenge = null; + _cause = cause; + _mainPrincipal = null; + } + + public AuthenticationResult(final byte[] challenge, final AuthenticationStatus status, final Exception cause) + { + if(status == AuthenticationStatus.SUCCESS) + { + throw new IllegalArgumentException("Successful authentication requires at least one principal"); + } + + _status = status; + _challenge = challenge; + _cause = cause; + _mainPrincipal = null; + } + + public Exception getCause() + { + return _cause; + } + + public AuthenticationStatus getStatus() + { + return _status; + } + + public byte[] getChallenge() + { + return _challenge; + } + + public Set getPrincipals() + { + return _principals; + } + + public Principal getMainPrincipal() + { + return _mainPrincipal; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/SubjectAuthenticationResult.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/SubjectAuthenticationResult.java new file mode 100644 index 0000000000..3be96b87eb --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/SubjectAuthenticationResult.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; + +import java.security.Principal; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.SubjectCreator; + +/** + * Encapsulates the result of an attempt to authenticate using a {@link SubjectCreator}. + * + *

+ * iff authentication was successful, {@link #getSubject()} will return a non-null value and + * {@link #getStatus()} will return {@link AuthenticationResult.AuthenticationStatus#SUCCESS}. + * + * In this case, the {@link Subject} will contain the user {@link Principal} and zero or more other principals + * representing groups. + *

+ * @see SubjectCreator + */ +public class SubjectAuthenticationResult +{ + private final AuthenticationResult _authenticationResult; + private final Subject _subject; + + public SubjectAuthenticationResult(AuthenticationResult authenticationResult, Subject subject) + { + _authenticationResult = authenticationResult; + _subject = subject; + } + + public SubjectAuthenticationResult(AuthenticationResult unsuccessfulAuthenticationResult) + { + this(unsuccessfulAuthenticationResult, null); + } + + public Exception getCause() + { + return _authenticationResult.getCause(); + } + + public AuthenticationResult.AuthenticationStatus getStatus() + { + return _authenticationResult.getStatus(); + } + + public byte[] getChallenge() + { + return _authenticationResult.getChallenge(); + } + + public Subject getSubject() + { + return _subject; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.java new file mode 100644 index 0000000000..5b3c1d59cf --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/UsernamePrincipal.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.security.auth; + +import java.io.Serializable; +import java.security.Principal; + +/** A principal that is just a wrapper for a simple username. */ +public class UsernamePrincipal implements Principal, Serializable +{ + private final String _name; + + public UsernamePrincipal(String name) + { + if (name == null) + { + throw new IllegalArgumentException("name cannot be null"); + } + _name = name; + } + + public String getName() + { + return _name; + } + + public String toString() + { + return _name; + } + + @Override + public int hashCode() + { + final int prime = 31; + return prime * _name.hashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else + { + if (obj instanceof UsernamePrincipal) + { + UsernamePrincipal other = (UsernamePrincipal) obj; + return _name.equals(other._name); + } + else + { + return false; + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java new file mode 100644 index 0000000000..cb5bc54cd2 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/AbstractPasswordFilePrincipalDatabase.java @@ -0,0 +1,465 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.UsernamePrincipal; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.security.Principal; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; + +public abstract class AbstractPasswordFilePrincipalDatabase implements PrincipalDatabase +{ + protected static final String DEFAULT_ENCODING = "utf-8"; + + private final Pattern _regexp = Pattern.compile(":"); + private final Map _userMap = new HashMap(); + private final ReentrantLock _userUpdate = new ReentrantLock(); + private final Random _random = new Random(); + private File _passwordFile; + + public final void open(File passwordFile) throws IOException + { + getLogger().info("PasswordFile using file " + passwordFile.getAbsolutePath()); + _passwordFile = passwordFile; + if (!passwordFile.exists()) + { + throw new FileNotFoundException("Cannot find password file " + passwordFile); + } + if (!passwordFile.canRead()) + { + throw new FileNotFoundException("Cannot read password file " + passwordFile + ". Check permissions."); + } + + loadPasswordFile(); + } + + /** + * SASL Callback Mechanism - sets the Password in the PasswordCallback based on the value in the PasswordFile + * If you want to change the password for a user, use updatePassword instead. + * + * @param principal The Principal to set the password for + * @param callback The PasswordCallback to call setPassword on + * + * @throws javax.security.auth.login.AccountNotFoundException If the Principal cannot be found in this Database + */ + public final 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); + } + } + + + /** + * Looks up the password for a specified user in the password file. Note this code is not 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. + */ + protected final char[] lookupPassword(String name) + { + U user = _userMap.get(name); + if (user == null) + { + return null; + } + else + { + return user.getPassword(); + } + } + + protected boolean compareCharArray(char[] a, char[] b) + { + boolean equal = false; + if (a.length == b.length) + { + equal = true; + int index = 0; + while (equal && index < a.length) + { + equal = a[index] == b[index]; + index++; + } + } + return equal; + } + + /** + * Changes the password for the specified user + * + * @param principal to change the password for + * @param password plaintext password to set the password too + */ + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + U user = _userMap.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + char[] orig = user.getPassword(); + _userUpdate.lock(); + try + { + user.setPassword(password); + + savePasswordFile(); + + return true; + } + catch (IOException e) + { + getLogger().error("Unable to save password file due to '" + e.getMessage() + + "', password change for user '" + principal + "' discarded"); + //revert the password change + user.restorePassword(orig); + + return false; + } + finally + { + _userUpdate.unlock(); + } + } + + + private void loadPasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + final Map newUserMap = new HashMap(); + + 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; + } + + U user = createUserFromFileData(result); + getLogger().info("Created user:" + user); + newUserMap.put(user.getName(), user); + } + } + finally + { + if (reader != null) + { + reader.close(); + } + } + + _userMap.clear(); + _userMap.putAll(newUserMap); + } + finally + { + _userUpdate.unlock(); + } + } + + protected abstract U createUserFromFileData(String[] result); + + + protected abstract Logger getLogger(); + + protected File createTempFileOnSameFilesystem() + { + File liveFile = _passwordFile; + File tmp; + + do + { + tmp = new File(liveFile.getPath() + _random.nextInt() + ".tmp"); + } + while(tmp.exists()); + + tmp.deleteOnExit(); + return tmp; + } + + protected void swapTempFileToLive(final File temp) throws IOException + { + File live = _passwordFile; + // Remove any existing ".old" file + final File old = new File(live.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + // Create an new ".old" file + if(!live.renameTo(old)) + { + //unable to rename the existing file to the backup name + getLogger().error("Could not backup the existing password file"); + throw new IOException("Could not backup the existing password file"); + } + + // Move temp file to be the new "live" file + if(!temp.renameTo(live)) + { + //failed to rename the new file to the required filename + if(!old.renameTo(live)) + { + //unable to return the backup to required filename + getLogger().error( + "Could not rename the new password file into place, and unable to restore original file"); + throw new IOException("Could not rename the new password file into place, and unable to restore original file"); + } + + getLogger().error("Could not rename the new password file into place"); + throw new IOException("Could not rename the new password file into place"); + } + } + + protected void savePasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + + BufferedReader reader = null; + PrintStream writer = null; + + File tmp = createTempFileOnSameFilesystem(); + + try + { + writer = new PrintStream(tmp); + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2 || result[0].startsWith("#")) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + continue; + } + + U user = _userMap.get(result[0]); + + if (user == null) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else if (!user.isDeleted()) + { + if (!user.isModified()) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else + { + byte[] encodedPassword = user.getEncodedPassword(); + + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(encodedPassword); + writer.println(); + + user.saved(); + } + } + } + + for (U user : _userMap.values()) + { + if (user.isModified()) + { + byte[] encodedPassword; + encodedPassword = user.getEncodedPassword(); + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(encodedPassword); + writer.println(); + user.saved(); + } + } + } + catch(IOException e) + { + getLogger().error("Unable to create the new password file: " + e); + throw new IOException("Unable to create the new password file",e); + } + finally + { + + try + { + if (reader != null) + { + reader.close(); + } + } + finally + { + if (writer != null) + { + writer.close(); + } + } + + } + + swapTempFileToLive(tmp); + } + finally + { + _userUpdate.unlock(); + } + } + + protected abstract U createUserFromPassword(Principal principal, char[] passwd); + + + public void reload() throws IOException + { + loadPasswordFile(); + } + + public List getUsers() + { + return new LinkedList(_userMap.values()); + } + + public Principal getUser(String username) + { + if (_userMap.containsKey(username)) + { + return new UsernamePrincipal(username); + } + return null; + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + U user = _userMap.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + try + { + _userUpdate.lock(); + user.delete(); + + try + { + savePasswordFile(); + } + catch (IOException e) + { + getLogger().error("Unable to remove user '" + user.getName() + "' from password file."); + return false; + } + + _userMap.remove(user.getName()); + } + finally + { + _userUpdate.unlock(); + } + + return true; + } + + public boolean createPrincipal(Principal principal, char[] password) + { + if (_userMap.get(principal.getName()) != null) + { + return false; + } + + U user = createUserFromPassword(principal, password); + + + try + { + _userUpdate.lock(); + _userMap.put(user.getName(), user); + + try + { + savePasswordFile(); + return true; + } + catch (IOException e) + { + //remove the use on failure. + _userMap.remove(user.getName()); + return false; + } + } + finally + { + _userUpdate.unlock(); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java new file mode 100644 index 0000000000..4f3892c2c6 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server.security.auth.database; + +import java.security.Principal; +import java.util.HashMap; +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HashedInitialiser; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HashedSaslServer; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexInitialiser; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexSaslServer; + +/** + * 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 extends AbstractPasswordFilePrincipalDatabase +{ + private final Logger _logger = Logger.getLogger(Base64MD5PasswordFilePrincipalDatabase.class); + private String _mechanismsString; + private final Map _callbackHandlerMap = new HashMap(); + + public Base64MD5PasswordFilePrincipalDatabase() + { + CRAMMD5HashedInitialiser crammd5HashedInitialiser = new CRAMMD5HashedInitialiser(); + crammd5HashedInitialiser.initialise(this); + _callbackHandlerMap.put(CRAMMD5HashedSaslServer.MECHANISM, crammd5HashedInitialiser.getCallbackHandler()); + + CRAMMD5HexInitialiser crammd5HexInitialiser = new CRAMMD5HexInitialiser(); + crammd5HexInitialiser.initialise(this); + _callbackHandlerMap.put(CRAMMD5HexSaslServer.MECHANISM, crammd5HexInitialiser.getCallbackHandler()); + + _mechanismsString = CRAMMD5HashedSaslServer.MECHANISM + " " + CRAMMD5HexSaslServer.MECHANISM; + } + + + /** + * Used to verify that the presented Password is correct. Currently only used by Management Console + * + * @param principal The principal to authenticate + * @param password The password to check + * + * @return true if password is correct + * + * @throws AccountNotFoundException if the principal cannot be found + */ + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + char[] pwd = lookupPassword(principal); + + if (pwd == null) + { + throw new AccountNotFoundException("Unable to lookup the specfied users password"); + } + + byte[] byteArray = new byte[password.length]; + int index = 0; + for (char c : password) + { + byteArray[index++] = (byte) c; + } + + byte[] MD5byteArray; + try + { + MD5byteArray = HashedUser.getMD5(byteArray); + } + catch (Exception e1) + { + getLogger().warn("Unable to hash password for user '" + principal + "' for comparison"); + return false; + } + + char[] hashedPassword = new char[MD5byteArray.length]; + + index = 0; + for (byte c : MD5byteArray) + { + hashedPassword[index++] = (char) c; + } + + return compareCharArray(pwd, hashedPassword); + } + + protected HashedUser createUserFromPassword(Principal principal, char[] passwd) + { + return new HashedUser(principal.getName(), passwd); + } + + + protected HashedUser createUserFromFileData(String[] result) + { + return new HashedUser(result); + } + + protected Logger getLogger() + { + return _logger; + } + + @Override + public String getMechanisms() + { + return _mechanismsString; + } + + @Override + public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException + { + CallbackHandler callbackHandler = _callbackHandlerMap.get(mechanism); + if(callbackHandler == null) + { + throw new SaslException("Unsupported mechanism: " + mechanism); + } + + //The SaslServers simply delegate to the built in CRAM-MD5 SaslServer + if(CRAMMD5HashedSaslServer.MECHANISM.equals(mechanism)) + { + return new CRAMMD5HashedSaslServer(mechanism, "AMQP", localFQDN, null, callbackHandler); + } + else if(CRAMMD5HexSaslServer.MECHANISM.equals(mechanism)) + { + return new CRAMMD5HexSaslServer(mechanism, "AMQP", localFQDN, null, callbackHandler); + } + + throw new SaslException("Unsupported mechanism: " + mechanism); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java new file mode 100644 index 0000000000..b9de1587b5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java @@ -0,0 +1,194 @@ +/* +* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.database; + +import org.apache.commons.codec.binary.Base64; +import org.apache.log4j.Logger; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + + +public class HashedUser implements PasswordPrincipal +{ + private static final Logger _logger = Logger.getLogger(HashedUser.class); + + private String _name; + private char[] _password; + private byte[] _encodedPassword = null; + private boolean _modified = false; + private boolean _deleted = false; + + HashedUser(String[] data) + { + if (data.length != 2) + { + throw new IllegalArgumentException("User Data should be length 2, username, password"); + } + + _name = data[0]; + + byte[] encoded_password; + try + { + encoded_password = data[1].getBytes(Base64MD5PasswordFilePrincipalDatabase.DEFAULT_ENCODING); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException("MD5 encoding not supported, even though the Java standard requires it",e); + } + + Base64 b64 = new Base64(); + byte[] decoded = b64.decode(encoded_password); + + _encodedPassword = encoded_password; + + _password = new char[decoded.length]; + + int index = 0; + for (byte c : decoded) + { + _password[index++] = (char) c; + } + } + + public HashedUser(String name, char[] password) + { + _name = name; + setPassword(password,false); + } + + public static byte[] getMD5(byte[] data) + { + MessageDigest md = null; + try + { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException e) + { + throw new RuntimeException("MD5 not supported although Java compliance requires it"); + } + + for (byte b : data) + { + md.update(b); + } + + return md.digest(); + } + + public String getName() + { + return _name; + } + + public String toString() + { + return _name; + } + + public char[] getPassword() + { + return _password; + } + + public void setPassword(char[] password) + { + setPassword(password, false); + } + + public void restorePassword(char[] password) + { + setPassword(password, true); + } + + void setPassword(char[] password, boolean alreadyHashed) + { + if(alreadyHashed){ + _password = password; + } + else + { + byte[] byteArray = new byte[password.length]; + int index = 0; + for (char c : password) + { + byteArray[index++] = (byte) c; + } + + byte[] MD5byteArray = getMD5(byteArray); + + _password = new char[MD5byteArray.length]; + + index = 0; + for (byte c : MD5byteArray) + { + _password[index++] = (char) c; + } + } + + _modified = true; + _encodedPassword = null; + } + + public byte[] getEncodedPassword() + { + if (_encodedPassword == null) + { + encodePassword(); + } + return _encodedPassword; + } + + private void encodePassword() + { + 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-core/src/main/java/org/apache/qpid/server/security/auth/database/PasswordPrincipal.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PasswordPrincipal.java new file mode 100644 index 0000000000..8e12d5f0a3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PasswordPrincipal.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.security.auth.database; + +import java.security.Principal; + +interface PasswordPrincipal extends Principal +{ + char[] getPassword(); + byte[] getEncodedPassword(); + + void setPassword(char[] password); + void restorePassword(char[] password); + + boolean isDeleted(); + + boolean isModified(); + + void saved(); + + void delete(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java new file mode 100644 index 0000000000..34d2710472 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.security.Principal; +import java.util.HashMap; +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.security.auth.sasl.amqplain.AmqPlainInitialiser; +import org.apache.qpid.server.security.auth.sasl.amqplain.AmqPlainSaslServer; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5Initialiser; +import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser; +import org.apache.qpid.server.security.auth.sasl.plain.PlainSaslServer; + +/** + * 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 extends AbstractPasswordFilePrincipalDatabase +{ + + private final Logger _logger = Logger.getLogger(PlainPasswordFilePrincipalDatabase.class); + private final Map _callbackHandlerMap = new HashMap(); + private String _mechanismsString; + + public PlainPasswordFilePrincipalDatabase() + { + AmqPlainInitialiser amqPlainInitialiser = new AmqPlainInitialiser(); + amqPlainInitialiser.initialise(this); + _callbackHandlerMap.put(AmqPlainSaslServer.MECHANISM, amqPlainInitialiser.getCallbackHandler()); + + PlainInitialiser plainInitialiser = new PlainInitialiser(); + plainInitialiser.initialise(this); + _callbackHandlerMap.put(PlainSaslServer.MECHANISM, plainInitialiser.getCallbackHandler()); + + CRAMMD5Initialiser crammd5Initialiser = new CRAMMD5Initialiser(); + crammd5Initialiser.initialise(this); + _callbackHandlerMap.put(CRAMMD5Initialiser.MECHANISM, crammd5Initialiser.getCallbackHandler()); + + _mechanismsString = AmqPlainSaslServer.MECHANISM + " " + PlainSaslServer.MECHANISM + " " + CRAMMD5Initialiser.MECHANISM; + } + + + /** + * Used to verify that the presented Password is correct. Currently only used by Management Console + * + * @param principal The principal to authenticate + * @param password The plaintext password to check + * + * @return true if password is correct + * + * @throws AccountNotFoundException if the principal cannot be found + */ + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + + char[] pwd = lookupPassword(principal); + + if (pwd == null) + { + throw new AccountNotFoundException("Unable to lookup the specfied users password"); + } + + return compareCharArray(pwd, password); + + } + + protected PlainUser createUserFromPassword(Principal principal, char[] passwd) + { + return new PlainUser(principal.getName(), passwd); + } + + + @Override + protected PlainUser createUserFromFileData(String[] result) + { + return new PlainUser(result); + } + + + protected Logger getLogger() + { + return _logger; + } + + + @Override + public String getMechanisms() + { + return _mechanismsString; + } + + @Override + public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException + { + CallbackHandler callbackHandler = _callbackHandlerMap.get(mechanism); + if(callbackHandler == null) + { + throw new SaslException("Unsupported mechanism: " + mechanism); + } + + if(CRAMMD5Initialiser.MECHANISM.equals(mechanism)) + { + //simply delegate to the built in CRAM-MD5 SaslServer + return Sasl.createSaslServer(mechanism, "AMQP", localFQDN, null, callbackHandler); + } + else if(PlainSaslServer.MECHANISM.equals(mechanism)) + { + return new PlainSaslServer(callbackHandler); + } + else if(AmqPlainSaslServer.MECHANISM.equals(mechanism)) + { + return new AmqPlainSaslServer(callbackHandler); + } + + throw new SaslException("Unsupported mechanism: " + mechanism); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java new file mode 100644 index 0000000000..bf9bfc6c99 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.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.security.auth.database; + +public class PlainUser implements PasswordPrincipal +{ + private String _name; + private char[] _password; + private boolean _modified = false; + private boolean _deleted = false; + + PlainUser(String[] data) + { + if (data.length != 2) + { + throw new IllegalArgumentException("User Data should be length 2, username, password"); + } + + _name = data[0]; + + _password = data[1].toCharArray(); + + } + + public PlainUser(String name, char[] password) + { + _name = name; + _password = password; + _modified = true; + } + + public String getName() + { + return _name; + } + + public String toString() + { + return _name; + } + + public char[] getPassword() + { + return _password; + } + + public byte[] getEncodedPassword() + { + byte[] byteArray = new byte[_password.length]; + int index = 0; + for (char c : _password) + { + byteArray[index++] = (byte) c; + } + return byteArray; + } + + + + public void restorePassword(char[] password) + { + setPassword(password); + } + + public void setPassword(char[] password) + { + _password = password; + _modified = true; + } + + public boolean isModified() + { + return _modified; + } + + public boolean isDeleted() + { + return _deleted; + } + + public void delete() + { + _deleted = true; + } + + public void saved() + { + _modified = false; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java new file mode 100644 index 0000000000..7e3e28e4f8 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.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.security.auth.database; + +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import java.io.File; +import java.io.IOException; +import java.security.Principal; +import java.util.List; +import java.util.Map; + +/** Represents a "user database" which is really a way of storing principals (i.e. usernames) and passwords. */ +public interface PrincipalDatabase +{ + void open(File passwordFile) throws IOException; + + /** + * Set the password for a given principal in the specified callback. This is used for certain SASL providers. The + * user database implementation should look up the password in any way it chooses and set it in the callback by + * calling its setPassword method. + * + * @param principal the principal + * @param callback the password callback that wants to receive the password + * + * @throws AccountNotFoundException if the account for specified principal could not be found + * @throws IOException if there was an error looking up the principal + */ + void setPassword(Principal principal, PasswordCallback callback) + throws IOException, AccountNotFoundException; + + /** + * Used to verify that the presented Password is correct. Currently only used by Management Console + * @param principal The principal to authenticate + * @param password The password to check + * @return true if password is correct + * @throws AccountNotFoundException if the principal cannot be found + */ + boolean verifyPassword(String principal, char[] password) + throws AccountNotFoundException; + + /** + * Update(Change) the password for the given principal + * @param principal Who's password is to be changed + * @param password The new password to use + * @return True if change was successful + * @throws AccountNotFoundException If the given principal doesn't exist in the Database + */ + boolean updatePassword(Principal principal, char[] password) + throws AccountNotFoundException; + + /** + * Create a new principal in the database + * @param principal The principal to create + * @param password The password to set for the principal + * @return True on a successful creation + */ + boolean createPrincipal(Principal principal, char[] password); + + /** + * Delete a principal + * @param principal The principal to delete + * @return True on a successful creation + * @throws AccountNotFoundException If the given principal doesn't exist in the Database + */ + boolean deletePrincipal(Principal principal) + throws AccountNotFoundException; + + /** + * Get the principal from the database with the given username + * @param username of the principal to lookup + * @return The Principal object for the given username or null if not found. + */ + Principal getUser(String username); + + /** + * Reload the database to its ensure contents are up to date + * @throws IOException If there was an error reloading the database + */ + void reload() throws IOException; + + List getUsers(); + + /** + * Get the list of mechanisms supported for use with the PrincipalDatabase + * @return space separated list of supported Sasl mechanisms + */ + public String getMechanisms(); + + public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException; +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/jmx/JMXPasswordAuthenticator.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/jmx/JMXPasswordAuthenticator.java new file mode 100644 index 0000000000..bf8d489e61 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/jmx/JMXPasswordAuthenticator.java @@ -0,0 +1,136 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.jmx; + +import java.net.SocketAddress; + +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; + +import javax.management.remote.JMXAuthenticator; +import javax.security.auth.Subject; + +public class JMXPasswordAuthenticator implements JMXAuthenticator +{ + static final String UNABLE_TO_LOOKUP = "The broker was unable to lookup the user details"; + static final String SHOULD_BE_STRING_ARRAY = "User details should be String[]"; + static final String SHOULD_HAVE_2_ELEMENTS = "User details should have 2 elements, username, password"; + static final String SHOULD_BE_NON_NULL = "Supplied username and password should be non-null"; + static final String INVALID_CREDENTIALS = "Invalid user details supplied"; + static final String USER_NOT_AUTHORISED_FOR_MANAGEMENT = "User not authorised for management"; + static final String CREDENTIALS_REQUIRED = "User details are required. " + + "Please ensure you are using an up to date management console to connect."; + + private final Broker _broker; + private final SocketAddress _address; + + public JMXPasswordAuthenticator(Broker broker, SocketAddress address) + { + _broker = broker; + _address = address; + } + + public Subject authenticate(Object credentials) throws SecurityException + { + validateCredentials(credentials); + + final String[] userCredentials = (String[]) credentials; + final String username = (String) userCredentials[0]; + final String password = (String) userCredentials[1]; + + final Subject authenticatedSubject = doAuthentication(username, password); + doManagementAuthorisation(authenticatedSubject); + return authenticatedSubject; + } + + private void validateCredentials(Object credentials) + { + // Verify that credential's are of type String[]. + if (!(credentials instanceof String[])) + { + if (credentials == null) + { + throw new SecurityException(CREDENTIALS_REQUIRED); + } + else + { + throw new SecurityException(SHOULD_BE_STRING_ARRAY); + } + } + + // Verify that required number of credentials. + if (((String[])credentials).length != 2) + { + throw new SecurityException(SHOULD_HAVE_2_ELEMENTS); + } + } + + private Subject doAuthentication(final String username, final String password) + { + // Verify that all required credentials are actually present. + if (username == null || password == null) + { + throw new SecurityException(SHOULD_BE_NON_NULL); + } + + SubjectCreator subjectCreator = _broker.getSubjectCreator(_address); + if (subjectCreator == null) + { + throw new SecurityException("Can't get subject creator for " + _address); + } + + final SubjectAuthenticationResult result = subjectCreator.authenticate(username, password); + + if (AuthenticationStatus.ERROR.equals(result.getStatus())) + { + throw new SecurityException("Authentication manager failed", result.getCause()); + } + else if (AuthenticationStatus.SUCCESS.equals(result.getStatus())) + { + return result.getSubject(); + } + else + { + throw new SecurityException(INVALID_CREDENTIALS); + } + } + + private void doManagementAuthorisation(Subject authenticatedSubject) + { + SecurityManager.setThreadSubject(authenticatedSubject); + try + { + if (!_broker.getSecurityManager().accessManagement()) + { + throw new SecurityException(USER_NOT_AUTHORISED_FOR_MANAGEMENT); + } + } + finally + { + SecurityManager.setThreadSubject(null); + } + } + + +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractPrincipalDatabaseAuthManagerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractPrincipalDatabaseAuthManagerFactory.java new file mode 100644 index 0000000000..5d427c4afb --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractPrincipalDatabaseAuthManagerFactory.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.manager; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +/** + * Factory for {@link PrincipalDatabaseAuthenticationManager} objects configured + * with either the Plain or Base64MD5 digest {@link PrincipalDatabase} + * implementation. + */ +public abstract class AbstractPrincipalDatabaseAuthManagerFactory implements AuthenticationManagerFactory +{ + public static final String RESOURCE_BUNDLE = "org.apache.qpid.server.security.auth.manager.PasswordFileAuthenticationProviderAttributeDescriptions"; + public static final String ATTRIBUTE_PATH = "path"; + + private static final Logger LOGGER = Logger.getLogger(AbstractPrincipalDatabaseAuthManagerFactory.class); + + public static final Collection ATTRIBUTES = Collections.unmodifiableList(Arrays.asList( + ATTRIBUTE_TYPE, + ATTRIBUTE_PATH)); + + + @Override + public AuthenticationManager createInstance(Map attributes) + { + if (attributes == null || !getType().equals(attributes.get(ATTRIBUTE_TYPE))) + { + return null; + } + + String passwordFile = (String) attributes.get(ATTRIBUTE_PATH); + if (passwordFile == null) + { + LOGGER.warn("Password file path must not be null"); + return null; + } + + PrincipalDatabase principalDatabase = createPrincipalDatabase(); + return new PrincipalDatabaseAuthenticationManager(principalDatabase, passwordFile); + } + + abstract PrincipalDatabase createPrincipalDatabase(); + + @Override + public Collection getAttributeNames() + { + return ATTRIBUTES; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java new file mode 100644 index 0000000000..30626ce98c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManager.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server.security.auth.manager; + +import java.security.Principal; + +import javax.security.auth.Subject; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.server.security.auth.sasl.anonymous.AnonymousSaslServer; + +public class AnonymousAuthenticationManager implements AuthenticationManager +{ + private static final String ANONYMOUS = "ANONYMOUS"; + + public static final String ANONYMOUS_USERNAME = "ANONYMOUS"; + + public static final Principal ANONYMOUS_PRINCIPAL = new UsernamePrincipal(ANONYMOUS_USERNAME); + + public static final Subject ANONYMOUS_SUBJECT = new Subject(); + static + { + ANONYMOUS_SUBJECT.getPrincipals().add(ANONYMOUS_PRINCIPAL); + } + + private static final AuthenticationResult ANONYMOUS_AUTHENTICATION = new AuthenticationResult(ANONYMOUS_PRINCIPAL); + + static final AnonymousAuthenticationManager INSTANCE = new AnonymousAuthenticationManager(); + + AnonymousAuthenticationManager() + { + } + + @Override + public void initialise() + { + + } + + @Override + public String getMechanisms() + { + return ANONYMOUS; + } + + @Override + public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException + { + if(ANONYMOUS.equals(mechanism)) + { + return new AnonymousSaslServer(); + } + else + { + throw new SaslException("Unknown mechanism: " + mechanism); + } + } + + @Override + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + try + { + // Process response from the client + byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]); + + if (server.isComplete()) + { + return ANONYMOUS_AUTHENTICATION; + } + else + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE); + } + } + catch (SaslException e) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e); + } + } + + @Override + public AuthenticationResult authenticate(String username, String password) + { + return ANONYMOUS_AUTHENTICATION; + } + + @Override + public void close() + { + } + + @Override + public void onCreate() + { + // nothing to do, no external resource is required + } + + @Override + public void onDelete() + { + // nothing to do, no external resource is used + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerFactory.java new file mode 100644 index 0000000000..91aba71720 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerFactory.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.manager; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; + +public class AnonymousAuthenticationManagerFactory implements AuthenticationManagerFactory +{ + public static final String PROVIDER_TYPE = "Anonymous"; + + @Override + public AuthenticationManager createInstance(Map attributes) + { + if (attributes != null && PROVIDER_TYPE.equals(attributes.get(ATTRIBUTE_TYPE))) + { + return new AnonymousAuthenticationManager(); + } + return null; + } + + @Override + public Collection getAttributeNames() + { + return Collections.singletonList(ATTRIBUTE_TYPE); + } + + @Override + public String getType() + { + return PROVIDER_TYPE; + } + + @Override + public Map getAttributeDescriptions() + { + return null; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java new file mode 100644 index 0000000000..67a4f348b1 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.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.server.security.auth.manager; + +import java.security.Principal; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import org.apache.qpid.common.Closeable; +import org.apache.qpid.server.security.auth.AuthenticationResult; + +/** + * Implementations of the AuthenticationManager are responsible for determining + * the authenticity of a user's credentials. + *

+ * If the authentication is successful, the manager is responsible for producing an + * {@link AuthenticationResult} containing the user's main {@link Principal} and zero or + * more other implementation-specific principals. + *

+ */ +public interface AuthenticationManager extends Closeable +{ + /** + * Initialise the authentication plugin. + * + */ + void initialise(); + + /** + * Gets the SASL mechanisms known to this manager. + * + * @return SASL mechanism names, space separated. + */ + String getMechanisms(); + + /** + * Creates a SASL server for the specified mechanism name for the given + * fully qualified domain name. + * + * @param mechanism mechanism name + * @param localFQDN domain name + * @param externalPrincipal externally authenticated Principal + * @return SASL server + * @throws SaslException + */ + SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException; + + /** + * Authenticates a user using SASL negotiation. + * + * @param server SASL server + * @param response SASL response to process + * + * @return authentication result + */ + AuthenticationResult authenticate(SaslServer server, byte[] response); + + /** + * Authenticates a user using their username and password. + * + * @param username username + * @param password password + * + * @return authentication result + */ + AuthenticationResult authenticate(String username, String password); + + /** + * Called after manager creation to create the required resources, for example, user databases etc. + */ + void onCreate(); + + /** + * Called before manager deletion to release and clean the resources created in {@link #onCreate()}. + */ + void onDelete(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/Base64MD5PasswordFileAuthenticationManagerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/Base64MD5PasswordFileAuthenticationManagerFactory.java new file mode 100644 index 0000000000..8b979c324d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/Base64MD5PasswordFileAuthenticationManagerFactory.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.security.auth.manager; + +import java.util.Map; + +import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.util.ResourceBundleLoader; + +public class Base64MD5PasswordFileAuthenticationManagerFactory extends AbstractPrincipalDatabaseAuthManagerFactory +{ + public static final String PROVIDER_TYPE = "Base64MD5PasswordFile"; + + @Override + public String getType() + { + return PROVIDER_TYPE; + } + + @Override + PrincipalDatabase createPrincipalDatabase() + { + return new Base64MD5PasswordFilePrincipalDatabase(); + } + + @Override + public Map getAttributeDescriptions() + { + return ResourceBundleLoader.getResources(RESOURCE_BUNDLE); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.java new file mode 100644 index 0000000000..c503549bf2 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManager.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.security.auth.manager; + +import java.security.Principal; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.server.security.auth.sasl.external.ExternalSaslServer; + +public class ExternalAuthenticationManager implements AuthenticationManager +{ + private static final String EXTERNAL = "EXTERNAL"; + + private boolean _useFullDN = false; + + ExternalAuthenticationManager(boolean useFullDN) + { + _useFullDN = useFullDN; + } + + @Override + public void initialise() + { + + } + + @Override + public String getMechanisms() + { + return EXTERNAL; + } + + @Override + public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException + { + if(EXTERNAL.equals(mechanism)) + { + return new ExternalSaslServer(externalPrincipal, _useFullDN); + } + else + { + throw new SaslException("Unknown mechanism: " + mechanism); + } + } + + @Override + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + // Process response from the client + try + { + server.evaluateResponse(response != null ? response : new byte[0]); + + Principal principal = ((ExternalSaslServer)server).getAuthenticatedPrincipal(); + + if(principal != null) + { + return new AuthenticationResult(principal); + } + else + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR); + } + } + catch (SaslException e) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR,e); + } + + } + + @Override + public AuthenticationResult authenticate(String username, String password) + { + return new AuthenticationResult(new UsernamePrincipal(username)); + } + + @Override + public void close() + { + } + + @Override + public void onCreate() + { + // nothing to do, no external resource is required + } + + @Override + public void onDelete() + { + // nothing to do, no external resource is used + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerFactory.java new file mode 100644 index 0000000000..6029674cd3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerFactory.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.manager; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.util.ResourceBundleLoader; + +public class ExternalAuthenticationManagerFactory implements AuthenticationManagerFactory +{ + public static final String RESOURCE_BUNDLE = "org.apache.qpid.server.security.auth.manager.ExternalAuthenticationProviderAttributeDescriptions"; + public static final String PROVIDER_TYPE = "External"; + public static final String ATTRIBUTE_USE_FULL_DN = "useFullDN"; + + public static final Collection ATTRIBUTES = Collections. unmodifiableList(Arrays.asList( + ATTRIBUTE_TYPE, + ATTRIBUTE_USE_FULL_DN)); + + @Override + public AuthenticationManager createInstance(Map attributes) + { + if (attributes != null && PROVIDER_TYPE.equals(attributes.get(ATTRIBUTE_TYPE))) + { + boolean useFullDN = Boolean.valueOf(String.valueOf(attributes.get(ATTRIBUTE_USE_FULL_DN))); + + return new ExternalAuthenticationManager(useFullDN); + } + return null; + } + + @Override + public Collection getAttributeNames() + { + return ATTRIBUTES; + } + + @Override + public String getType() + { + return PROVIDER_TYPE; + } + + @Override + public Map getAttributeDescriptions() + { + return ResourceBundleLoader.getResources(RESOURCE_BUNDLE); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationProviderAttributeDescriptions.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationProviderAttributeDescriptions.properties new file mode 100644 index 0000000000..263254e9fe --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationProviderAttributeDescriptions.properties @@ -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. + +useFullDN=Use the full DN as the Username \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.java new file mode 100644 index 0000000000..6bbf3ca6f5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManager.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.security.auth.manager; + +import java.io.IOException; +import java.security.Principal; +import java.util.HashMap; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.UsernamePrincipal; + +public class KerberosAuthenticationManager implements AuthenticationManager +{ + private static final String GSSAPI_MECHANISM = "GSSAPI"; + private final CallbackHandler _callbackHandler = new GssApiCallbackHandler(); + + KerberosAuthenticationManager() + { + } + + @Override + public void initialise() + { + + } + + @Override + public String getMechanisms() + { + return GSSAPI_MECHANISM; + } + + @Override + public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException + { + if(GSSAPI_MECHANISM.equals(mechanism)) + { + try + { + return Sasl.createSaslServer(GSSAPI_MECHANISM, "AMQP", localFQDN, + new HashMap(), _callbackHandler); + } + catch (SaslException e) + { + e.printStackTrace(System.err); + throw e; + } + } + else + { + throw new SaslException("Unknown mechanism: " + mechanism); + } + } + + @Override + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + try + { + // Process response from the client + byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]); + + if (server.isComplete()) + { + return new AuthenticationResult(new UsernamePrincipal(server.getAuthorizationID())); + } + else + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE); + } + } + catch (SaslException e) + { + e.printStackTrace(System.err); + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e); + } + } + + @Override + public AuthenticationResult authenticate(String username, String password) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR); + } + + @Override + public void close() + { + } + + @Override + public void onCreate() + { + // nothing to do, no external resource is required + } + + @Override + public void onDelete() + { + // nothing to do, no external resource is used + } + + private static class GssApiCallbackHandler implements CallbackHandler + { + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + for(Callback callback : callbacks) + { + if (callback instanceof AuthorizeCallback) + { + ((AuthorizeCallback) callback).setAuthorized(true); + } + else + { + throw new UnsupportedCallbackException(callback); + } + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManagerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManagerFactory.java new file mode 100644 index 0000000000..a2d5bd4c8e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/KerberosAuthenticationManagerFactory.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.manager; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; + +public class KerberosAuthenticationManagerFactory implements AuthenticationManagerFactory +{ + public static final String PROVIDER_TYPE = "Kerberos"; + + @Override + public AuthenticationManager createInstance(Map attributes) + { + if (attributes != null && PROVIDER_TYPE.equals(attributes.get(ATTRIBUTE_TYPE))) + { + return new KerberosAuthenticationManager(); + } + return null; + } + + @Override + public Collection getAttributeNames() + { + return Collections.singletonList(ATTRIBUTE_TYPE); + } + + @Override + public String getType() + { + return PROVIDER_TYPE; + } + + @Override + public Map getAttributeDescriptions() + { + return null; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PasswordFileAuthenticationProviderAttributeDescriptions.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PasswordFileAuthenticationProviderAttributeDescriptions.properties new file mode 100644 index 0000000000..e847e90f57 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PasswordFileAuthenticationProviderAttributeDescriptions.properties @@ -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. + +path=File location* \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PlainPasswordFileAuthenticationManagerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PlainPasswordFileAuthenticationManagerFactory.java new file mode 100644 index 0000000000..5e077cfe79 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PlainPasswordFileAuthenticationManagerFactory.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.manager; + +import java.util.Map; + +import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.util.ResourceBundleLoader; + +public class PlainPasswordFileAuthenticationManagerFactory extends AbstractPrincipalDatabaseAuthManagerFactory +{ + public static final String PROVIDER_TYPE = "PlainPasswordFile"; + + @Override + public String getType() + { + return PROVIDER_TYPE; + } + + @Override + PrincipalDatabase createPrincipalDatabase() + { + return new PlainPasswordFilePrincipalDatabase(); + } + + @Override + public Map getAttributeDescriptions() + { + return ResourceBundleLoader.getResources(AbstractPrincipalDatabaseAuthManagerFactory.RESOURCE_BUNDLE); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java new file mode 100644 index 0000000000..da6464a57e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server.security.auth.manager; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.Principal; + +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +public class PrincipalDatabaseAuthenticationManager implements AuthenticationManager +{ + private final PrincipalDatabase _principalDatabase; + private final String _passwordFile; + + public PrincipalDatabaseAuthenticationManager(PrincipalDatabase pd, String passwordFile) + { + _principalDatabase = pd; + _passwordFile = passwordFile; + } + + public void initialise() + { + try + { + _principalDatabase.open(new File(_passwordFile)); + } + catch (FileNotFoundException e) + { + throw new IllegalConfigurationException("Exception opening password database: " + e.getMessage(), e); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot use password database at :" + _passwordFile, e); + } + } + + public String getMechanisms() + { + return _principalDatabase.getMechanisms(); + } + + public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException + { + return _principalDatabase.createSaslServer(mechanism, localFQDN, externalPrincipal); + } + + /** + * @see org.apache.qpid.server.security.auth.manager.AuthenticationManager#authenticate(SaslServer, byte[]) + */ + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + try + { + // Process response from the client + byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]); + + if (server.isComplete()) + { + final String userId = server.getAuthorizationID(); + return new AuthenticationResult(new UsernamePrincipal(userId)); + } + else + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE); + } + } + catch (SaslException e) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e); + } + } + + /** + * @see org.apache.qpid.server.security.auth.manager.AuthenticationManager#authenticate(String, String) + */ + public AuthenticationResult authenticate(final String username, final String password) + { + try + { + if (_principalDatabase.verifyPassword(username, password.toCharArray())) + { + return new AuthenticationResult(new UsernamePrincipal(username)); + } + else + { + return new AuthenticationResult(AuthenticationStatus.CONTINUE); + } + } + catch (AccountNotFoundException e) + { + return new AuthenticationResult(AuthenticationStatus.CONTINUE); + } + } + + public void close() + { + + } + + public PrincipalDatabase getPrincipalDatabase() + { + return _principalDatabase; + } + + @Override + public void onCreate() + { + try + { + File passwordFile = new File(_passwordFile); + if (!passwordFile.exists()) + { + passwordFile.createNewFile(); + } + else if (!passwordFile.canRead()) + { + throw new IllegalConfigurationException("Cannot read password file" + _passwordFile + ". Check permissions."); + } + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot use password database at :" + _passwordFile, e); + } + } + + @Override + public void onDelete() + { + File file = new File(_passwordFile); + if (file.exists() && file.isFile()) + { + file.delete(); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManager.java new file mode 100644 index 0000000000..903f54dd8e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManager.java @@ -0,0 +1,214 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.qpid.server.security.auth.manager; + +import java.io.IOException; +import java.security.Principal; +import java.util.Collections; +import java.util.HashMap; +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.sasl.AuthorizeCallback; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.server.security.auth.sasl.plain.PlainPasswordCallback; +import org.apache.qpid.server.security.auth.sasl.plain.PlainSaslServer; + +public class SimpleAuthenticationManager implements AuthenticationManager +{ + private static final Logger _logger = Logger.getLogger(SimpleAuthenticationManager.class); + + private static final String PLAIN_MECHANISM = "PLAIN"; + private static final String CRAM_MD5_MECHANISM = "CRAM-MD5"; + + private Map _users; + + public SimpleAuthenticationManager(String userName, String userPassword) + { + this(Collections.singletonMap(userName, userPassword)); + } + + public SimpleAuthenticationManager(Map users) + { + _users = new HashMap(users); + } + + @Override + public void initialise() + { + } + + @Override + public String getMechanisms() + { + return PLAIN_MECHANISM + " " + CRAM_MD5_MECHANISM; + } + + @Override + public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException + { + if (PLAIN_MECHANISM.equals(mechanism)) + { + return new PlainSaslServer(new SimplePlainCallbackHandler()); + } + else if (CRAM_MD5_MECHANISM.equals(mechanism)) + { + return Sasl.createSaslServer(mechanism, "AMQP", localFQDN, null, new SimpleCramMd5CallbackHandler()); + } + else + { + throw new SaslException("Unknown mechanism: " + mechanism); + } + } + + @Override + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + try + { + // Process response from the client + byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]); + + if (server.isComplete()) + { + String authorizationID = server.getAuthorizationID(); + _logger.debug("Authenticated as " + authorizationID); + + return new AuthenticationResult(new UsernamePrincipal(authorizationID)); + } + else + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE); + } + } + catch (SaslException e) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e); + } + } + + @Override + public AuthenticationResult authenticate(String username, String password) + { + if (_users.containsKey(username)) + { + String userPassword = _users.get(username); + if (userPassword.equals(password)) + { + return new AuthenticationResult(new UsernamePrincipal(username)); + } + } + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR); + } + + @Override + public void close() + { + } + + @Override + public void onCreate() + { + // nothing to do, no external resource is required + } + + @Override + public void onDelete() + { + // nothing to do, no external resource is used + } + + private class SimpleCramMd5CallbackHandler implements CallbackHandler + { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + String username = null; + for (Callback callback : callbacks) + { + if (callback instanceof NameCallback) + { + username = ((NameCallback) callback).getDefaultName(); + } + else if (callback instanceof PasswordCallback) + { + if (_users.containsKey(username)) + { + String password = _users.get(username); + ((PasswordCallback) callback).setPassword(password.toCharArray()); + } + else + { + throw new SaslException("Authentication failed"); + } + } + else if (callback instanceof AuthorizeCallback) + { + ((AuthorizeCallback) callback).setAuthorized(true); + } + else + { + throw new UnsupportedCallbackException(callback); + } + } + } + } + + private class SimplePlainCallbackHandler implements CallbackHandler + { + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + String username = null; + for (Callback callback : callbacks) + { + if (callback instanceof NameCallback) + { + username = ((NameCallback) callback).getDefaultName(); + } + else if (callback instanceof PlainPasswordCallback) + { + if (_users.containsKey(username)) + { + PlainPasswordCallback plainPasswordCallback = (PlainPasswordCallback) callback; + String password = plainPasswordCallback.getPlainPassword(); + plainPasswordCallback.setAuthenticated(password.equals(_users.get(username))); + } + } + else if (callback instanceof AuthorizeCallback) + { + ((AuthorizeCallback) callback).setAuthorized(true); + } + else + { + throw new UnsupportedCallbackException(callback); + } + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java new file mode 100644 index 0000000000..0db0d388d6 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManager.java @@ -0,0 +1,317 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 java.io.IOException; +import java.security.Principal; +import java.util.Hashtable; + +import javax.naming.AuthenticationException; +import javax.naming.Context; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import org.apache.log4j.Logger; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.server.security.auth.sasl.plain.PlainPasswordCallback; +import org.apache.qpid.server.security.auth.sasl.plain.PlainSaslServer; + +public class SimpleLDAPAuthenticationManager implements AuthenticationManager +{ + private static final Logger _logger = Logger.getLogger(SimpleLDAPAuthenticationManager.class); + + private final String _providerSearchURL; + private final String _providerAuthURL; + private final String _searchContext; + private final String _searchFilter; + private final String _ldapContextFactory; + + SimpleLDAPAuthenticationManager(String providerSearchUrl, String providerAuthUrl, String searchContext, String searchFilter, String ldapContextFactory) + { + _providerSearchURL = providerSearchUrl; + _providerAuthURL = providerAuthUrl; + _searchContext = searchContext; + _searchFilter = searchFilter; + _ldapContextFactory = ldapContextFactory; + } + + @Override + public void initialise() + { + validateInitialDirContext(); + } + + @Override + public String getMechanisms() + { + return PlainSaslServer.MECHANISM; + } + + @Override + public SaslServer createSaslServer(String mechanism, String localFQDN, Principal externalPrincipal) throws SaslException + { + if(PlainSaslServer.MECHANISM.equals(mechanism)) + { + return new PlainSaslServer(new SimpleLDAPPlainCallbackHandler()); + } + else + { + throw new SaslException("Unknown mechanism: " + mechanism); + } + } + + @Override + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + try + { + // Process response from the client + byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]); + + if (server.isComplete()) + { + String authorizationID = server.getAuthorizationID(); + _logger.debug("Authenticated as " + authorizationID); + + return new AuthenticationResult(new UsernamePrincipal(authorizationID)); + } + else + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE); + } + } + catch (SaslException e) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e); + } + } + + @Override + public AuthenticationResult authenticate(String username, String password) + { + try + { + AuthenticationResult result = doLDAPNameAuthentication(getNameFromId(username), password); + if(result.getStatus() == AuthenticationStatus.SUCCESS) + { + //Return a result based on the supplied username rather than the search name + return new AuthenticationResult(new UsernamePrincipal(username)); + } + else + { + return result; + } + } + catch (NamingException e) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e); + } + } + + private AuthenticationResult doLDAPNameAuthentication(String name, String password) + { + if(name == null) + { + //The search didn't return anything, class as not-authenticated before it NPEs below + return new AuthenticationResult(AuthenticationStatus.CONTINUE); + } + + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, _ldapContextFactory); + env.put(Context.PROVIDER_URL, _providerAuthURL); + + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + + env.put(Context.SECURITY_PRINCIPAL, name); + env.put(Context.SECURITY_CREDENTIALS, password); + + DirContext ctx = null; + try + { + ctx = new InitialDirContext(env); + + //Authentication succeeded + return new AuthenticationResult(new UsernamePrincipal(name)); + } + catch(AuthenticationException ae) + { + //Authentication failed + return new AuthenticationResult(AuthenticationStatus.CONTINUE); + } + catch (NamingException e) + { + //Some other failure + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e); + } + finally + { + if(ctx != null) + { + try + { + ctx.close(); + } + catch (Exception e) + { + _logger.warn("Exception closing InitialDirContext", e); + } + } + } + } + + @Override + public void close() + { + } + + private void validateInitialDirContext() + { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, _ldapContextFactory); + env.put(Context.PROVIDER_URL, _providerSearchURL); + env.put(Context.SECURITY_AUTHENTICATION, "none"); + + try + { + new InitialDirContext(env).close(); + } + catch (NamingException e) + { + throw new RuntimeException("Unable to establish anonymous connection to the ldap server at " + _providerSearchURL, e); + } + } + + private class SimpleLDAPPlainCallbackHandler implements CallbackHandler + { + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + String name = null; + String password = null; + AuthenticationResult authenticated = null; + for(Callback callback : callbacks) + { + if (callback instanceof NameCallback) + { + String id = ((NameCallback) callback).getDefaultName(); + try + { + name = getNameFromId(id); + } + catch (NamingException e) + { + _logger.warn("SASL Authentication Exception", e); + } + if(password != null) + { + authenticated = doLDAPNameAuthentication(name, password); + } + } + else if (callback instanceof PlainPasswordCallback) + { + password = ((PlainPasswordCallback)callback).getPlainPassword(); + if(name != null) + { + authenticated = doLDAPNameAuthentication(name, password); + if(authenticated.getStatus()== AuthenticationResult.AuthenticationStatus.SUCCESS) + { + ((PlainPasswordCallback)callback).setAuthenticated(true); + } + } + } + else if (callback instanceof AuthorizeCallback) + { + ((AuthorizeCallback) callback).setAuthorized(authenticated != null && authenticated.getStatus() == AuthenticationResult.AuthenticationStatus.SUCCESS); + } + else + { + throw new UnsupportedCallbackException(callback); + } + } + } + } + + private String getNameFromId(String id) throws NamingException + { + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, _ldapContextFactory); + env.put(Context.PROVIDER_URL, _providerSearchURL); + + env.put(Context.SECURITY_AUTHENTICATION, "none"); + DirContext ctx = null; + + ctx = new InitialDirContext(env); + + try + { + SearchControls searchControls = new SearchControls(); + searchControls.setReturningAttributes(new String[] {}); + searchControls.setCountLimit(1l); + searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); + NamingEnumeration namingEnum = null; + String name = null; + + namingEnum = ctx.search(_searchContext, _searchFilter, new String[] { id }, searchControls); + if(namingEnum.hasMore()) + { + SearchResult result = (SearchResult) namingEnum.next(); + name = result.getNameInNamespace(); + } + return name; + } + finally + { + try + { + ctx.close(); + } + catch (Exception e) + { + _logger.warn("Exception closing InitialDirContext", e); + } + } + + } + + @Override + public void onCreate() + { + // nothing to do, no external resource is required + } + + @Override + public void onDelete() + { + // nothing to do, no external resource is used + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerFactory.java new file mode 100644 index 0000000000..55e90178f8 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerFactory.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.server.security.auth.manager; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.util.ResourceBundleLoader; + +public class SimpleLDAPAuthenticationManagerFactory implements AuthenticationManagerFactory +{ + public static final String RESOURCE_BUNDLE = "org.apache.qpid.server.security.auth.manager.SimpleLDAPAuthenticationProviderAttributeDescriptions"; + private static final String DEFAULT_LDAP_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory"; + + public static final String PROVIDER_TYPE = "SimpleLDAP"; + + public static final String ATTRIBUTE_LDAP_CONTEXT_FACTORY = "ldapContextFactory"; + public static final String ATTRIBUTE_SEARCH_FILTER = "searchFilter"; + public static final String ATTRIBUTE_SEARCH_CONTEXT = "searchContext"; + public static final String ATTRIBUTE_PROVIDER_AUTH_URL = "providerAuthUrl"; + public static final String ATTRIBUTE_PROVIDER_URL = "providerUrl"; + + public static final Collection ATTRIBUTES = Collections. unmodifiableList(Arrays.asList( + ATTRIBUTE_TYPE, + ATTRIBUTE_PROVIDER_URL, + ATTRIBUTE_SEARCH_CONTEXT, + ATTRIBUTE_SEARCH_FILTER, + ATTRIBUTE_PROVIDER_AUTH_URL, + ATTRIBUTE_LDAP_CONTEXT_FACTORY + )); + + @Override + public AuthenticationManager createInstance(Map attributes) + { + if (attributes == null || !PROVIDER_TYPE.equals(attributes.get(ATTRIBUTE_TYPE))) + { + return null; + } + String providerUrl = (String) attributes.get(ATTRIBUTE_PROVIDER_URL); + String providerAuthUrl = (String) attributes.get(ATTRIBUTE_PROVIDER_AUTH_URL); + if (providerAuthUrl == null) + { + providerAuthUrl = providerUrl; + } + String searchContext = (String) attributes.get(ATTRIBUTE_SEARCH_CONTEXT); + String searchFilter = (String) attributes.get(ATTRIBUTE_SEARCH_FILTER); + String ldapContextFactory = (String) attributes.get(ATTRIBUTE_LDAP_CONTEXT_FACTORY); + if (ldapContextFactory == null) + { + ldapContextFactory = DEFAULT_LDAP_CONTEXT_FACTORY; + } + + return new SimpleLDAPAuthenticationManager(providerUrl, providerAuthUrl, searchContext, searchFilter, + ldapContextFactory); + } + + @Override + public Collection getAttributeNames() + { + return ATTRIBUTES; + } + + @Override + public String getType() + { + return PROVIDER_TYPE; + } + + @Override + public Map getAttributeDescriptions() + { + return ResourceBundleLoader.getResources(RESOURCE_BUNDLE); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationProviderAttributeDescriptions.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationProviderAttributeDescriptions.properties new file mode 100644 index 0000000000..5439c7fff5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationProviderAttributeDescriptions.properties @@ -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. + +ldapContextFactory= LDAP context factory +searchFilter=Search filter* +searchContext=Search context* +providerAuthUrl=LDAP authentication URL +providerUrl=LDAP server URL* \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java new file mode 100644 index 0000000000..4cbf5096df --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.sasl; + +import javax.security.auth.callback.CallbackHandler; + +public interface AuthenticationProviderInitialiser +{ + /** + * @return the mechanism's name. This will be used in the list of mechanism's advertised to the + * client. + */ + String getMechanismName(); + + /** + * @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 must be + * fully threadsafe. + */ + CallbackHandler getCallbackHandler(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java new file mode 100644 index 0000000000..f36ef1516c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.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.security.auth.sasl; + +import org.apache.log4j.Logger; + +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +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.security.Principal; + +public abstract class UsernamePasswordInitialiser implements AuthenticationProviderInitialiser +{ + protected static final Logger _logger = Logger.getLogger(UsernamePasswordInitialiser.class); + + private ServerCallbackHandler _callbackHandler; + + private static 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(PrincipalDatabase db) + { + if (db == null) + { + throw new NullPointerException("Cannot initialise with a null Principal database."); + } + + _callbackHandler = new ServerCallbackHandler(db); + } + + public CallbackHandler getCallbackHandler() + { + return _callbackHandler; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java new file mode 100644 index 0000000000..8f8686db88 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java @@ -0,0 +1,31 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.sasl.amqplain; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class AmqPlainInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "AMQPLAIN"; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java new file mode 100644 index 0000000000..a4c4fff42f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java @@ -0,0 +1,132 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.sasl.amqplain; + +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableFactory; + +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 java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; + +public class AmqPlainSaslServer implements SaslServer +{ + public static final String MECHANISM = "AMQPLAIN"; + + private CallbackHandler _cbh; + + private String _authorizationId; + + private boolean _complete = false; + + public AmqPlainSaslServer(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + try + { + final FieldTable ft = FieldTableFactory.newFieldTable(new DataInputStream(new ByteArrayInputStream(response)), response.length); + String username = 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 = ft.getString("PASSWORD"); + AuthorizeCallback authzCb = new AuthorizeCallback(username, username); + Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb}; + _cbh.handle(callbacks); + String storedPwd = new String(passwordCb.getPassword()); + if (storedPwd.equals(pwd)) + { + _complete = true; + } + if (authzCb.isAuthorized() && _complete) + { + _authorizationId = authzCb.getAuthenticationID(); + return null; + } + else + { + throw new SaslException("Authentication failed"); + } + } + catch (AMQFrameDecodingException e) + { + throw new SaslException("Unable to decode response: " + e, e); + } + catch (IOException e) + { + throw new SaslException("Error processing data: " + e, e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Unable to obtain data from callback handler: " + e, e); + } + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return _authorizationId; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + _cbh = null; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java new file mode 100644 index 0000000000..3a73f577fe --- /dev/null +++ b/qpid/java/broker-core/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 javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; +import java.util.Map; + +public class 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 != null && + (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-core/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java new file mode 100644 index 0000000000..d10193e743 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.sasl.anonymous; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManager; + + +public class AnonymousSaslServer implements SaslServer +{ + public static final String MECHANISM = "ANONYMOUS"; + + private boolean _complete = false; + + public AnonymousSaslServer() + { + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + _complete = true; + return null; + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return AnonymousAuthenticationManager.ANONYMOUS_PRINCIPAL.getName(); + } + + 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 + { + } +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServerFactory.java new file mode 100644 index 0000000000..4650234972 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServerFactory.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.anonymous; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; +import java.util.Map; + +public class AnonymousSaslServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (AnonymousSaslServer.MECHANISM.equals(mechanism)) + { + return new AnonymousSaslServer(); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props != null && + (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE) || + props.containsKey(Sasl.POLICY_NOANONYMOUS))) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{AnonymousSaslServer.MECHANISM}; + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java new file mode 100644 index 0000000000..fc324b686a --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.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.sasl.crammd5; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class CRAMMD5HashedInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return CRAMMD5HashedSaslServer.MECHANISM; + } + + public void initialise(PrincipalDatabase passwordFile) + { + super.initialise(passwordFile); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java new file mode 100644 index 0000000000..a2d9fa5e3e --- /dev/null +++ b/qpid/java/broker-core/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.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; +import java.util.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 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-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java new file mode 100644 index 0000000000..4e82940439 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.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.crammd5; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; +import java.util.Map; + +public class CRAMMD5HashedServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map 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-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.java new file mode 100644 index 0000000000..c4c4b4e0cf --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.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.crammd5; + +import java.io.File; +import java.io.IOException; +import java.security.Principal; +import java.util.List; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class CRAMMD5HexInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return CRAMMD5HexSaslServer.MECHANISM; + } + + public void initialise(PrincipalDatabase db) + { + super.initialise(new HexifyPrincipalDatabase(db)); + + } + + private static class HexifyPrincipalDatabase implements PrincipalDatabase + { + private PrincipalDatabase _realPricipalDatabase; + + HexifyPrincipalDatabase(PrincipalDatabase db) + { + _realPricipalDatabase = db; + } + + private char[] toHex(char[] password) + { + StringBuilder sb = new StringBuilder(); + for (char c : password) + { + //toHexString does not prepend 0 so we have to + if (((byte) c > -1) && (byte) c < 0x10 ) + { + sb.append(0); + } + + sb.append(Integer.toHexString(c & 0xFF)); + } + + //Extract the hex string as char[] + char[] hex = new char[sb.length()]; + + sb.getChars(0, sb.length(), hex, 0); + + return hex; + } + + public void setPassword(Principal principal, PasswordCallback callback) throws IOException, AccountNotFoundException + { + //Let the read DB set the password + _realPricipalDatabase.setPassword(principal, callback); + + //Retrieve the setpassword + char[] plainPassword = callback.getPassword(); + + char[] hexPassword = toHex(plainPassword); + + callback.setPassword(hexPassword); + } + + // Simply delegate to the real PrincipalDB + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + return _realPricipalDatabase.verifyPassword(principal, password); + } + + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + return _realPricipalDatabase.updatePassword(principal, password); + } + + public boolean createPrincipal(Principal principal, char[] password) + { + return _realPricipalDatabase.createPrincipal(principal, password); + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + return _realPricipalDatabase.deletePrincipal(principal); + } + + public Principal getUser(String username) + { + return _realPricipalDatabase.getUser(username); + } + + public List getUsers() + { + return _realPricipalDatabase.getUsers(); + } + + public void reload() throws IOException + { + _realPricipalDatabase.reload(); + } + + @Override + public void open(File passwordFile) throws IOException + { + throw new UnsupportedOperationException(); + } + + @Override + public String getMechanisms() + { + return _realPricipalDatabase.getMechanisms(); + } + + @Override + public SaslServer createSaslServer(String mechanism, String localFQDN, + Principal externalPrincipal) throws SaslException + { + return _realPricipalDatabase.createSaslServer(mechanism, localFQDN, externalPrincipal); + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexSaslServer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexSaslServer.java new file mode 100644 index 0000000000..e19baaa7c6 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexSaslServer.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.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; +import java.util.Enumeration; +import java.util.Map; + +public class CRAMMD5HexSaslServer implements SaslServer +{ + public static final String MECHANISM = "CRAM-MD5-HEX"; + + private SaslServer _realServer; + + public CRAMMD5HexSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + Enumeration factories = Sasl.getSaslServerFactories(); + + while (factories.hasMoreElements()) + { + SaslServerFactory factory = (SaslServerFactory) factories.nextElement(); + + if (factory instanceof CRAMMD5HexServerFactory) + { + 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-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexServerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexServerFactory.java new file mode 100644 index 0000000000..06c9108a73 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexServerFactory.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.crammd5; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; +import java.util.Map; + +public class CRAMMD5HexServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (mechanism.equals(CRAMMD5HexSaslServer.MECHANISM)) + { + return new CRAMMD5HexSaslServer(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[]{CRAMMD5HexSaslServer.MECHANISM}; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java new file mode 100644 index 0000000000..70c13233b0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java @@ -0,0 +1,33 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.sasl.crammd5; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class CRAMMD5Initialiser extends UsernamePasswordInitialiser +{ + public static final String MECHANISM = "CRAM-MD5"; + + public String getMechanismName() + { + return MECHANISM; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/external/ExternalSaslServer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/external/ExternalSaslServer.java new file mode 100644 index 0000000000..e1007d91e0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/external/ExternalSaslServer.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.security.auth.sasl.external; + +import java.security.Principal; + +import javax.security.auth.x500.X500Principal; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.transport.network.security.ssl.SSLUtil; + +public class ExternalSaslServer implements SaslServer +{ + private static final Logger LOGGER = Logger.getLogger(ExternalSaslServer.class); + + public static final String MECHANISM = "EXTERNAL"; + + private boolean _complete = false; + private final Principal _externalPrincipal; + private boolean _useFullDN = false; + + public ExternalSaslServer(Principal externalPrincipal, boolean useFullDN) + { + _useFullDN = useFullDN; + _externalPrincipal = externalPrincipal; + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + _complete = true; + return null; + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return getAuthenticatedPrincipal().getName(); + } + + 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 + { + } + + public Principal getAuthenticatedPrincipal() + { + if (_externalPrincipal instanceof X500Principal && !_useFullDN) + { + // Construct username as @...... + String username; + String dn = ((X500Principal) _externalPrincipal).getName(X500Principal.RFC2253); + + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("Parsing username from Principal DN: " + dn); + } + + username = SSLUtil.getIdFromSubjectDN(dn); + if (username.isEmpty()) + { + // CN is empty => Cannot construct username => Authentication failed => return null + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("CN value was empty in Principal name, unable to construct username"); + } + return null; + } + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("Constructing Principal with username: " + username); + } + return new UsernamePrincipal(username); + } + else + { + if(LOGGER.isDebugEnabled()) + { + LOGGER.debug("Using external Principal: " + _externalPrincipal); + } + return _externalPrincipal; + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java new file mode 100644 index 0000000000..05acd3b27f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java @@ -0,0 +1,31 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.sasl.plain; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class PlainInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "PLAIN"; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainPasswordCallback.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainPasswordCallback.java new file mode 100644 index 0000000000..0ea2f3c92e --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainPasswordCallback.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.security.auth.sasl.plain; + +import javax.security.auth.callback.PasswordCallback; +import java.util.Arrays; + +/** + * Custom PasswordCallback for use during the PLAIN authentication process. + * + * To be used in combination with PrincipalDatabase implementations that + * can either set a plain text value in the parent callback, or use the + * setAuthenticated(bool) method after observing the incoming plain text. + * + * isAuthenticated() should then be used to determine the final result. + * + */ +public class PlainPasswordCallback extends PasswordCallback +{ + private char[] _plainPassword; + private boolean _authenticated = false; + + /** + * Constructs a new PlainPasswordCallback with the incoming plain text password. + * + * @throws NullPointerException if the incoming plain text is null + */ + public PlainPasswordCallback(String prompt, boolean echoOn, String plainPassword) + { + super(prompt, echoOn); + + if(plainPassword == null) + { + throw new NullPointerException("Incoming plain text cannot be null"); + } + + _plainPassword = plainPassword.toCharArray(); + } + + public String getPlainPassword() + { + return new String(_plainPassword); + } + + public void setAuthenticated(boolean authenticated) + { + _authenticated = authenticated; + } + + /** + * Method to determine if the incoming plain password is authenticated + * + * @return true if the stored password matches the incoming text, or setAuthenticated(true) has been called + */ + public boolean isAuthenticated() + { + char[] storedPassword = getPassword(); + + return Arrays.equals(_plainPassword, storedPassword) || _authenticated; + } +} + diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java new file mode 100644 index 0000000000..a811806c00 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.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.auth.sasl.plain; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import java.io.IOException; + +public class PlainSaslServer implements SaslServer +{ + public static final String MECHANISM = "PLAIN"; + + private CallbackHandler _cbh; + + private String _authorizationId; + + private boolean _complete = false; + + public PlainSaslServer(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + 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"); + } + + PlainPasswordCallback passwordCb; + AuthorizeCallback authzCb; + + try + { + // we do not currently support authcid in any meaningful way + String authzid = new String(response, authzidNullPosition + 1, authcidNullPosition - authzidNullPosition - 1, "utf8"); + + // 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"); + + // we do not care about the prompt but it throws if null + NameCallback nameCb = new NameCallback("prompt", authzid); + passwordCb = new PlainPasswordCallback("prompt", false, pwd); + authzCb = new AuthorizeCallback(authzid, authzid); + + Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb}; + _cbh.handle(callbacks); + + } + catch (IOException e) + { + if(e instanceof SaslException) + { + throw (SaslException) e; + } + throw new SaslException("Error processing data: " + e, e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Unable to obtain data from callback handler: " + e, e); + } + + if (passwordCb.isAuthenticated()) + { + _complete = true; + } + + if (authzCb.isAuthorized() && _complete) + { + _authorizationId = authzCb.getAuthenticationID(); + return null; + } + else + { + throw new SaslException("Authentication failed"); + } + } + + + + 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-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java new file mode 100644 index 0000000000..445e5ef812 --- /dev/null +++ b/qpid/java/broker-core/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 javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; +import java.util.Map; + +public class PlainSaslServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (PlainSaslServer.MECHANISM.equals(mechanism)) + { + return new PlainSaslServer(cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props != null && + (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-core/src/main/java/org/apache/qpid/server/security/group/FileGroupDatabase.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupDatabase.java new file mode 100644 index 0000000000..c66e7fd4e4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupDatabase.java @@ -0,0 +1,287 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.group; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.Date; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; + +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + +/** + * A group database that reads/writes the following file format: + * + * group1.users=user1,user2 + * group2.users=user2,user3 + */ +public class FileGroupDatabase implements GroupDatabase +{ + private static final Logger LOGGER = Logger.getLogger(FileGroupDatabase.class); + + private Map> _groupToUserMap = new ConcurrentHashMap>(); + private Map> _userToGroupMap = new ConcurrentHashMap>(); + private String _groupFile; + + @Override + public Set getAllGroups() + { + return Collections.unmodifiableSet(_groupToUserMap.keySet()); + } + + public synchronized void setGroupFile(String groupFile) throws IOException + { + File file = new File(groupFile); + + if (!file.canRead()) + { + throw new FileNotFoundException(groupFile + + " cannot be found or is not readable"); + } + + readGroupFile(groupFile); + } + + @Override + public Set getUsersInGroup(String group) + { + if (group == null) + { + LOGGER.warn("Requested user set for null group. Returning empty set."); + return Collections.emptySet(); + } + + Set set = _groupToUserMap.get(group); + if (set == null) + { + return Collections.emptySet(); + } + else + { + return Collections.unmodifiableSet(set); + } + } + + @Override + public synchronized void addUserToGroup(String user, String group) + { + Set users = _groupToUserMap.get(group); + if (users == null) + { + throw new IllegalArgumentException("Group " + group + " does not exist so could not add " + user + " to it"); + } + + users.add(user); + + Set groups = _userToGroupMap.get(user); + if (groups == null) + { + groups = new ConcurrentSkipListSet(); + _userToGroupMap.put(user, groups); + } + groups.add(group); + + update(); + } + + @Override + public synchronized void removeUserFromGroup(String user, String group) + { + Set users = _groupToUserMap.get(group); + if (users == null) + { + throw new IllegalArgumentException("Group " + group + " does not exist so could not remove " + user + " from it"); + } + + users.remove(user); + + Set groups = _userToGroupMap.get(user); + if (groups != null) + { + groups.remove(group); + } + + update(); + } + + @Override + public Set getGroupsForUser(String user) + { + if(user == null) + { + LOGGER.warn("Requested group set for null user. Returning empty set."); + return Collections.emptySet(); + } + + Set groups = _userToGroupMap.get(user); + if (groups == null) + { + return Collections.emptySet(); + } + else + { + return Collections.unmodifiableSet(groups); + } + } + + @Override + public synchronized void createGroup(String group) + { + Set users = new ConcurrentSkipListSet(); + _groupToUserMap.put(group, users); + + update(); + } + + @Override + public synchronized void removeGroup(String group) + { + _groupToUserMap.remove(group); + for (Set groupsForUser : _userToGroupMap.values()) + { + groupsForUser.remove(group); + } + + update(); + } + + private synchronized void update() + { + if (_groupFile != null) + { + try + { + writeGroupFile(_groupFile); + } + catch (IOException e) + { + throw new RuntimeException("Unable to persist change to file " + _groupFile); + } + } + } + + private synchronized void readGroupFile(String groupFile) throws IOException + { + _groupFile = groupFile; + _groupToUserMap.clear(); + _userToGroupMap.clear(); + Properties propertiesFile = new Properties(); + FileInputStream fileInputStream = new FileInputStream(groupFile); + try + { + propertiesFile.load(fileInputStream); + } + finally + { + if(fileInputStream != null) + { + fileInputStream.close(); + } + } + + for (String propertyName : propertiesFile.stringPropertyNames()) + { + validatePropertyNameIsGroupName(propertyName); + + String groupName = propertyName.replaceAll("\\.users$", ""); + String userString = propertiesFile.getProperty(propertyName); + + final Set userSet = buildUserSetFromCommaSeparateValue(userString); + + _groupToUserMap.put(groupName, userSet); + + for (String userName : userSet) + { + Set groupsForThisUser = _userToGroupMap.get(userName); + + if (groupsForThisUser == null) + { + groupsForThisUser = new ConcurrentSkipListSet(); + _userToGroupMap.put(userName, groupsForThisUser); + } + + groupsForThisUser.add(groupName); + } + } + } + + private synchronized void writeGroupFile(String groupFile) throws IOException + { + Properties propertiesFile = new Properties(); + + for (String group : _groupToUserMap.keySet()) + { + Set users = _groupToUserMap.get(group); + String userList = StringUtils.join(users, ","); + + propertiesFile.setProperty(group + ".users", userList); + } + + String comment = "Written " + new Date(); + FileOutputStream fileOutputStream = new FileOutputStream(groupFile); + try + { + propertiesFile.store(fileOutputStream, comment); + } + finally + { + if(fileOutputStream != null) + { + fileOutputStream.close(); + } + } + } + + private void validatePropertyNameIsGroupName(String propertyName) + { + if (!propertyName.endsWith(".users")) + { + throw new IllegalArgumentException( + "Invalid definition with name '" + + propertyName + + "'. Group definitions must end with suffix '.users'"); + } + } + + private ConcurrentSkipListSet buildUserSetFromCommaSeparateValue(String userString) + { + String[] users = userString.split(","); + final ConcurrentSkipListSet userSet = new ConcurrentSkipListSet(); + for (String user : users) + { + final String trimmed = user.trim(); + if (!trimmed.isEmpty()) + { + userSet.add(trimmed); + } + } + return userSet; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupManager.java new file mode 100644 index 0000000000..e11a4f83db --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupManager.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.security.group; + +import java.io.File; +import java.io.IOException; +import java.security.Principal; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.security.auth.UsernamePrincipal; + +/** + * Implementation of a group manager whose implementation is backed by a flat group file. + *

+ * This plugin is configured in the following manner: + *

+ *
+ * "groupproviders":[
+ * ...
+ * {
+ *  "name" : "...",
+ *  "type" : "GroupFile",
+ *  "path" : "path/to/file/with/groups",
+ * }
+ * ...
+ * ]
+ * 
+ */ +public class FileGroupManager implements GroupManager +{ + private final FileGroupDatabase _groupDatabase; + private final String _groupFile; + + public FileGroupManager(String groupFile) + { + _groupFile = groupFile; + _groupDatabase = new FileGroupDatabase(); + } + + @Override + public Set getGroupPrincipalsForUser(String userId) + { + Set groups = _groupDatabase.getGroupsForUser(userId); + if (groups.isEmpty()) + { + return Collections.emptySet(); + } + else + { + Set principals = new HashSet(); + for (String groupName : groups) + { + principals.add(new GroupPrincipal(groupName)); + } + return principals; + } + } + + @Override + public Set getUserPrincipalsForGroup(String group) + { + Set users = _groupDatabase.getUsersInGroup(group); + if (users.isEmpty()) + { + return Collections.emptySet(); + } + else + { + Set principals = new HashSet(); + for (String user : users) + { + principals.add(new UsernamePrincipal(user)); + } + return principals; + } + } + + @Override + public Set getGroupPrincipals() + { + Set groups = _groupDatabase.getAllGroups(); + if (groups.isEmpty()) + { + return Collections.emptySet(); + } + else + { + Set principals = new HashSet(); + for (String groupName : groups) + { + principals.add(new GroupPrincipal(groupName)); + } + return principals; + } + } + + @Override + public void createGroup(String group) + { + _groupDatabase.createGroup(group); + } + + @Override + public void removeGroup(String group) + { + _groupDatabase.removeGroup(group); + } + + @Override + public void addUserToGroup(String user, String group) + { + _groupDatabase.addUserToGroup(user, group); + } + + @Override + public void removeUserFromGroup(String user, String group) + { + _groupDatabase.removeUserFromGroup(user, group); + + } + + @Override + public void onDelete() + { + File file = new File(_groupFile); + if (file.exists()) + { + if (!file.delete()) + { + throw new IllegalConfigurationException("Cannot delete group file"); + } + } + } + + @Override + public void onCreate() + { + File file = new File(_groupFile); + if (!file.exists()) + { + File parent = file.getParentFile(); + if (!parent.exists()) + { + parent.mkdirs(); + } + if (parent.exists()) + { + try + { + file.createNewFile(); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Cannot create group file"); + } + } + else + { + throw new IllegalConfigurationException("Cannot create group file"); + } + } + } + + @Override + public void open() + { + try + { + _groupDatabase.setGroupFile(_groupFile); + } + catch (IOException e) + { + throw new IllegalConfigurationException("Unable to set group file " + _groupFile, e); + } + } + + @Override + public void close() + { + // no-op + } + + @Override + public int hashCode() + { + return ((_groupFile == null) ? 0 : _groupFile.hashCode()); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + FileGroupManager other = (FileGroupManager) obj; + if (_groupFile == null) + { + if (other._groupFile != null) + { + return false; + } + else + { + return true; + } + } + return _groupFile.equals(other._groupFile); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupManagerFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupManagerFactory.java new file mode 100644 index 0000000000..50f08623cd --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupManagerFactory.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.security.group; + +import static org.apache.qpid.server.util.MapValueConverter.getStringAttribute; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.plugin.GroupManagerFactory; +import org.apache.qpid.server.util.ResourceBundleLoader; + +public class FileGroupManagerFactory implements GroupManagerFactory +{ + public static final String RESOURCE_BUNDLE = "org.apache.qpid.server.security.group.FileGroupProviderAttributeDescriptions"; + + public static final String GROUP_FILE_PROVIDER_TYPE = "GroupFile"; + public static final String PATH = "path"; + + public static final Collection ATTRIBUTES = Collections. unmodifiableList(Arrays.asList( + ATTRIBUTE_TYPE, + PATH + )); + + @Override + public GroupManager createInstance(Map attributes) + { + if(attributes == null || !GROUP_FILE_PROVIDER_TYPE.equals(attributes.get(ATTRIBUTE_TYPE))) + { + return null; + } + + String groupFile = getStringAttribute(PATH, attributes, null); + if (groupFile == null || "".equals(groupFile.trim())) + { + throw new IllegalConfigurationException("Path to file containing groups is not specified!"); + } + + return new FileGroupManager(groupFile); + } + + @Override + public String getType() + { + return GROUP_FILE_PROVIDER_TYPE; + } + + @Override + public Collection getAttributeNames() + { + return ATTRIBUTES; + } + + @Override + public Map getAttributeDescriptions() + { + return ResourceBundleLoader.getResources(RESOURCE_BUNDLE); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupProviderAttributeDescriptions.properties b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupProviderAttributeDescriptions.properties new file mode 100644 index 0000000000..2c2d2ab9e3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/FileGroupProviderAttributeDescriptions.properties @@ -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. + +path= File location* diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupDatabase.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupDatabase.java new file mode 100644 index 0000000000..98c12782d8 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupDatabase.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.group; + +import java.util.Set; + +public interface GroupDatabase +{ + Set getAllGroups(); + Set getUsersInGroup(String group); + + void addUserToGroup(String user, String group); + void removeUserFromGroup(String user, String group); + Set getGroupsForUser(String user); + void createGroup(String group); + void removeGroup(String group); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupManager.java new file mode 100644 index 0000000000..3fd6dd2800 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupManager.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.group; + +import java.security.Principal; +import java.util.Set; + +public interface GroupManager +{ + Set getGroupPrincipalsForUser(String user); + + Set getGroupPrincipals(); + + Set getUserPrincipalsForGroup(String group); + + void createGroup(String group); + + void removeGroup(String group); + + void addUserToGroup(String user, String group); + + void removeUserFromGroup(String user, String group); + + void open(); + + void close(); + + void onDelete(); + + void onCreate(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupPrincipal.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupPrincipal.java new file mode 100644 index 0000000000..a9590bb964 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/security/group/GroupPrincipal.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.group; + +import java.io.Serializable; +import java.security.Principal; +import java.security.acl.Group; +import java.util.Enumeration; + +/** + * Immutable representation of a user group. In Qpid, groups do not know + * about their membership, and therefore the {@link #addMember(Principal)} + * methods etc throw {@link UnsupportedOperationException}. + * + */ +public class GroupPrincipal implements Group, Serializable +{ + /** Name of the group */ + private final String _groupName; + + public GroupPrincipal(final String groupName) + { + _groupName = groupName; + } + + public String getName() + { + return _groupName; + } + + public boolean addMember(Principal user) + { + throw new UnsupportedOperationException("Not supported"); + } + + public boolean removeMember(Principal user) + { + throw new UnsupportedOperationException("Not supported"); + } + + public boolean isMember(Principal member) + { + throw new UnsupportedOperationException("Not supported"); + } + + public Enumeration members() + { + throw new UnsupportedOperationException("Not supported"); + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + final int prime = 37; + return prime * _groupName.hashCode(); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else + { + if (obj instanceof GroupPrincipal) + { + GroupPrincipal other = (GroupPrincipal) obj; + return _groupName.equals(other._groupName); + } + else + { + return false; + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/stats/StatisticsCounter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/stats/StatisticsCounter.java new file mode 100644 index 0000000000..f382f90010 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/stats/StatisticsCounter.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.stats; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicLong; + +/** + * This class collects statistics and counts the total, rate per second and + * peak rate per second values for the events that are registered with it. + */ +public class StatisticsCounter +{ + private static final Logger _log = LoggerFactory.getLogger(StatisticsCounter.class); + + public static final long DEFAULT_SAMPLE_PERIOD = Long.getLong("qpid.statistics.samplePeriod", 2000L); // 2s + + private static final String COUNTER = "counter"; + private static final AtomicLong _counterIds = new AtomicLong(0L); + + private long _peak = 0L; + private long _total = 0L; + private long _temp = 0L; + private long _last = 0L; + private long _rate = 0L; + + private long _start; + + private final long _period; + private final String _name; + + public StatisticsCounter() + { + this(COUNTER); + } + + public StatisticsCounter(String name) + { + this(name, DEFAULT_SAMPLE_PERIOD); + } + + public StatisticsCounter(String name, long period) + { + _period = period; + _name = name + "-" + + _counterIds.incrementAndGet(); + reset(); + } + + public void registerEvent() + { + registerEvent(1L); + } + + public void registerEvent(long value) + { + registerEvent(value, System.currentTimeMillis()); + } + + public void registerEvent(long value, long timestamp) + { + long thisSample = (timestamp / _period); + synchronized (this) + { + if (thisSample > _last) + { + _last = thisSample; + _rate = _temp; + _temp = 0L; + if (_rate > _peak) + { + _peak = _rate; + } + } + + _total += value; + _temp += value; + } + } + + /** + * Update the current rate and peak - may reset rate to zero if a new + * sample period has started. + */ + private void update() + { + registerEvent(0L, System.currentTimeMillis()); + } + + /** + * Reset + */ + public void reset() + { + _log.info("Resetting statistics for counter: " + _name); + _peak = 0L; + _rate = 0L; + _total = 0L; + _start = System.currentTimeMillis(); + _last = _start / _period; + } + + public double getPeak() + { + update(); + return (double) _peak / ((double) _period / 1000.0d); + } + + public double getRate() + { + update(); + return (double) _rate / ((double) _period / 1000.0d); + } + + public long getTotal() + { + return _total; + } + + public long getStart() + { + return _start; + } + + public Date getStartTime() + { + return new Date(_start); + } + + public String getName() + { + return _name; + } + + public long getPeriod() + { + return _period; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/stats/StatisticsGatherer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/stats/StatisticsGatherer.java new file mode 100644 index 0000000000..37d87bb628 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/stats/StatisticsGatherer.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.stats; + +/** + * This interface is to be implemented by any broker business object that + * wishes to gather statistics about messages delivered through it. + * + * These statistics are exposed using a separate JMX Mbean interface, which + * calls these methods to retrieve the underlying {@link StatisticsCounter}s + * and return their attributes. This interface gives a standard way for + * parts of the broker to set up and configure statistics generation. + *

+ * When creating these objects, there should be a parent/child relationship + * between them, such that the lowest level gatherer can record staticics if + * enabled, and pass on the notification to the parent object to allow higher + * level aggregation. When resetting statistics, this works in the opposite + * direction, with higher level gatherers also resetting all of their children. + */ +public interface StatisticsGatherer +{ + /** + * Initialise the statistics gathering for this object. + * + * This method is responsible for creating any {@link StatisticsCounter} + * objects and for determining whether statistics generation should be + * enabled, by checking broker and system configuration. + * + * @see StatisticsCounter#DISABLE_STATISTICS + */ + void initialiseStatistics(); + + /** + * This method is responsible for registering the receipt of a message + * with the counters, and also for passing this notification to any parent + * {@link StatisticsGatherer}s. If statistics generation is not enabled, + * then this method should simple delegate to the parent gatherer. + * + * @param messageSize the size in bytes of the delivered message + * @param timestamp the time the message was delivered + */ + void registerMessageReceived(long messageSize, long timestamp); + + /** + * This method is responsible for registering the delivery of a message + * with the counters. Message delivery is recorded by the counter using + * the current system time, as opposed to the message timestamp. + * + * @param messageSize the size in bytes of the delivered message + * @see #registerMessageReceived(long, long) + */ + void registerMessageDelivered(long messageSize); + + /** + * Gives access to the {@link StatisticsCounter} that is used to count + * delivered message statistics. + * + * @return the {@link StatisticsCounter} that counts delivered messages + */ + StatisticsCounter getMessageDeliveryStatistics(); + + /** + * Gives access to the {@link StatisticsCounter} that is used to count + * received message statistics. + * + * @return the {@link StatisticsCounter} that counts received messages + */ + StatisticsCounter getMessageReceiptStatistics(); + + /** + * Gives access to the {@link StatisticsCounter} that is used to count + * delivered message size statistics. + * + * @return the {@link StatisticsCounter} that counts delivered bytes + */ + StatisticsCounter getDataDeliveryStatistics(); + + /** + * Gives access to the {@link StatisticsCounter} that is used to count + * received message size statistics. + * + * @return the {@link StatisticsCounter} that counts received bytes + */ + StatisticsCounter getDataReceiptStatistics(); + + /** + * Reset the counters for this, and any child {@link StatisticsGatherer}s. + */ + void resetStatistics(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractDurableConfiguredObjectRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractDurableConfiguredObjectRecoverer.java new file mode 100644 index 0000000000..dbe8bf22a0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractDurableConfiguredObjectRecoverer.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.store; + +import java.util.Map; +import java.util.UUID; + +public abstract class AbstractDurableConfiguredObjectRecoverer implements DurableConfiguredObjectRecoverer +{ + @Override + public void load(final DurableConfigurationRecoverer durableConfigurationRecoverer, + final UUID id, + final Map attributes) + { + final UnresolvedObject obj = createUnresolvedObject(id, getType(), attributes); + UnresolvedDependency[] dependencies = obj.getUnresolvedDependencies(); + for(final UnresolvedDependency dependency : dependencies) + { + Object dep; + if((dep = durableConfigurationRecoverer.getResolvedObject(dependency.getType(), dependency.getId())) != null) + { + dependency.resolve(dep); + } + else + { + durableConfigurationRecoverer.addResolutionListener(dependency.getType(), dependency.getId(), + new DependencyListener() + { + + @Override + public void dependencyResolved(final String depType, + final UUID depId, + final Object o) + { + dependency.resolve(o); + if(obj.getUnresolvedDependencies().length == 0) + { + durableConfigurationRecoverer.resolve(getType(), id, obj.resolve()); + } + } + }); + } + } + if(obj.getUnresolvedDependencies().length == 0) + { + durableConfigurationRecoverer.resolve(getType(), id, obj.resolve()); + } + else + { + durableConfigurationRecoverer.addUnresolvedObject(getType(), id, obj); + } + + } + + public abstract UnresolvedObject createUnresolvedObject(final UUID id, + final String type, + final Map attributes); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractJDBCMessageStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractJDBCMessageStore.java new file mode 100644 index 0000000000..4a1452d86c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractJDBCMessageStore.java @@ -0,0 +1,2363 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* 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 java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.ref.SoftReference; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.plugin.MessageMetaDataType; +import org.apache.qpid.server.queue.AMQQueue; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; + +abstract public class AbstractJDBCMessageStore implements MessageStore, DurableConfigurationStore +{ + private static final String DB_VERSION_TABLE_NAME = "QPID_DB_VERSION"; + private static final String CONFIGURATION_VERSION_TABLE_NAME = "QPID_CONFIG_VERSION"; + + private static final String QUEUE_ENTRY_TABLE_NAME = "QPID_QUEUE_ENTRIES"; + + private static final String META_DATA_TABLE_NAME = "QPID_MESSAGE_METADATA"; + private static final String MESSAGE_CONTENT_TABLE_NAME = "QPID_MESSAGE_CONTENT"; + + private static final String LINKS_TABLE_NAME = "QPID_LINKS"; + private static final String BRIDGES_TABLE_NAME = "QPID_BRIDGES"; + + private static final String XID_TABLE_NAME = "QPID_XIDS"; + private static final String XID_ACTIONS_TABLE_NAME = "QPID_XID_ACTIONS"; + + private static final String CONFIGURED_OBJECTS_TABLE_NAME = "QPID_CONFIGURED_OBJECTS"; + private static final int DEFAULT_CONFIG_VERSION = 0; + + public static String[] ALL_TABLES = new String[] { DB_VERSION_TABLE_NAME, LINKS_TABLE_NAME, BRIDGES_TABLE_NAME, XID_ACTIONS_TABLE_NAME, + XID_TABLE_NAME, QUEUE_ENTRY_TABLE_NAME, MESSAGE_CONTENT_TABLE_NAME, META_DATA_TABLE_NAME, CONFIGURED_OBJECTS_TABLE_NAME, CONFIGURATION_VERSION_TABLE_NAME }; + + private static final int DB_VERSION = 7; + + private final AtomicLong _messageId = new AtomicLong(0); + private AtomicBoolean _closed = new AtomicBoolean(false); + + private static final String CREATE_DB_VERSION_TABLE = "CREATE TABLE "+ DB_VERSION_TABLE_NAME + " ( version int not null )"; + private static final String INSERT_INTO_DB_VERSION = "INSERT INTO "+ DB_VERSION_TABLE_NAME + " ( version ) VALUES ( ? )"; + private static final String SELECT_FROM_DB_VERSION = "SELECT version FROM " + DB_VERSION_TABLE_NAME; + private static final String UPDATE_DB_VERSION = "UPDATE " + DB_VERSION_TABLE_NAME + " SET version = ?"; + + + private static final String CREATE_CONFIG_VERSION_TABLE = "CREATE TABLE "+ CONFIGURATION_VERSION_TABLE_NAME + " ( version int not null )"; + private static final String INSERT_INTO_CONFIG_VERSION = "INSERT INTO "+ CONFIGURATION_VERSION_TABLE_NAME + " ( version ) VALUES ( ? )"; + private static final String SELECT_FROM_CONFIG_VERSION = "SELECT version FROM " + CONFIGURATION_VERSION_TABLE_NAME; + private static final String UPDATE_CONFIG_VERSION = "UPDATE " + CONFIGURATION_VERSION_TABLE_NAME + " SET version = ?"; + + + private static final String INSERT_INTO_QUEUE_ENTRY = "INSERT INTO " + QUEUE_ENTRY_TABLE_NAME + " (queue_id, message_id) values (?,?)"; + private static final String DELETE_FROM_QUEUE_ENTRY = "DELETE FROM " + QUEUE_ENTRY_TABLE_NAME + " WHERE queue_id = ? AND message_id =?"; + private static final String SELECT_FROM_QUEUE_ENTRY = "SELECT queue_id, message_id FROM " + QUEUE_ENTRY_TABLE_NAME + " ORDER BY queue_id, message_id"; + private static final String INSERT_INTO_MESSAGE_CONTENT = "INSERT INTO " + MESSAGE_CONTENT_TABLE_NAME + + "( message_id, content ) values (?, ?)"; + private static final String SELECT_FROM_MESSAGE_CONTENT = "SELECT content FROM " + MESSAGE_CONTENT_TABLE_NAME + + " WHERE message_id = ?"; + private static final String DELETE_FROM_MESSAGE_CONTENT = "DELETE FROM " + MESSAGE_CONTENT_TABLE_NAME + + " WHERE message_id = ?"; + + private static final String INSERT_INTO_META_DATA = "INSERT INTO " + META_DATA_TABLE_NAME + "( message_id , meta_data ) values (?, ?)"; + private static final String SELECT_FROM_META_DATA = + "SELECT meta_data FROM " + META_DATA_TABLE_NAME + " WHERE message_id = ?"; + private static final String DELETE_FROM_META_DATA = "DELETE FROM " + META_DATA_TABLE_NAME + " WHERE message_id = ?"; + private static final String SELECT_ALL_FROM_META_DATA = "SELECT message_id, meta_data FROM " + META_DATA_TABLE_NAME; + + private static final String SELECT_FROM_LINKS = + "SELECT create_time, arguments FROM " + LINKS_TABLE_NAME + " WHERE id_lsb = ? and id_msb"; + private static final String DELETE_FROM_LINKS = "DELETE FROM " + LINKS_TABLE_NAME + + " WHERE id_lsb = ? and id_msb = ?"; + private static final String SELECT_ALL_FROM_LINKS = "SELECT id_lsb, id_msb, create_time, " + + "arguments FROM " + LINKS_TABLE_NAME; + private static final String FIND_LINK = "SELECT id_lsb, id_msb FROM " + LINKS_TABLE_NAME + " WHERE id_lsb = ? and" + + " id_msb = ?"; + private static final String INSERT_INTO_LINKS = "INSERT INTO " + LINKS_TABLE_NAME + "( id_lsb, " + + "id_msb, create_time, arguments ) values (?, ?, ?, ?)"; + private static final String SELECT_FROM_BRIDGES = + "SELECT create_time, link_id_lsb, link_id_msb, arguments FROM " + + BRIDGES_TABLE_NAME + " WHERE id_lsb = ? and id_msb = ?"; + private static final String DELETE_FROM_BRIDGES = "DELETE FROM " + BRIDGES_TABLE_NAME + + " WHERE id_lsb = ? and id_msb = ?"; + private static final String SELECT_ALL_FROM_BRIDGES = "SELECT id_lsb, id_msb, " + + " create_time," + + " link_id_lsb, link_id_msb, " + + "arguments FROM " + BRIDGES_TABLE_NAME + + " WHERE link_id_lsb = ? and link_id_msb = ?"; + private static final String FIND_BRIDGE = "SELECT id_lsb, id_msb FROM " + BRIDGES_TABLE_NAME + + " WHERE id_lsb = ? and id_msb = ?"; + private static final String INSERT_INTO_BRIDGES = "INSERT INTO " + BRIDGES_TABLE_NAME + "( id_lsb, id_msb, " + + "create_time, " + + "link_id_lsb, link_id_msb, " + + "arguments )" + + " values (?, ?, ?, ?, ?, ?)"; + + private static final String INSERT_INTO_XIDS = + "INSERT INTO "+ XID_TABLE_NAME +" ( format, global_id, branch_id ) values (?, ?, ?)"; + private static final String DELETE_FROM_XIDS = "DELETE FROM " + XID_TABLE_NAME + + " WHERE format = ? and global_id = ? and branch_id = ?"; + private static final String SELECT_ALL_FROM_XIDS = "SELECT format, global_id, branch_id FROM " + XID_TABLE_NAME; + private static final String INSERT_INTO_XID_ACTIONS = + "INSERT INTO "+ XID_ACTIONS_TABLE_NAME +" ( format, global_id, branch_id, action_type, " + + "queue_id, message_id ) values (?,?,?,?,?,?) "; + private static final String DELETE_FROM_XID_ACTIONS = "DELETE FROM " + XID_ACTIONS_TABLE_NAME + + " WHERE format = ? and global_id = ? and branch_id = ?"; + private static final String SELECT_ALL_FROM_XID_ACTIONS = + "SELECT action_type, queue_id, message_id FROM " + XID_ACTIONS_TABLE_NAME + + " WHERE format = ? and global_id = ? and branch_id = ?"; + private static final String INSERT_INTO_CONFIGURED_OBJECTS = "INSERT INTO " + CONFIGURED_OBJECTS_TABLE_NAME + + " ( id, object_type, attributes) VALUES (?,?,?)"; + private static final String UPDATE_CONFIGURED_OBJECTS = "UPDATE " + CONFIGURED_OBJECTS_TABLE_NAME + + " set object_type =?, attributes = ? where id = ?"; + private static final String DELETE_FROM_CONFIGURED_OBJECTS = "DELETE FROM " + CONFIGURED_OBJECTS_TABLE_NAME + + " where id = ?"; + private static final String FIND_CONFIGURED_OBJECT = "SELECT object_type, attributes FROM " + CONFIGURED_OBJECTS_TABLE_NAME + + " where id = ?"; + private static final String SELECT_FROM_CONFIGURED_OBJECTS = "SELECT id, object_type, attributes FROM " + CONFIGURED_OBJECTS_TABLE_NAME; + + protected static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); + + protected final EventManager _eventManager = new EventManager(); + + protected final StateManager _stateManager; + + private MessageStoreRecoveryHandler _messageRecoveryHandler; + private TransactionLogRecoveryHandler _tlogRecoveryHandler; + private ConfigurationRecoveryHandler _configRecoveryHandler; + private VirtualHost _virtualHost; + + public AbstractJDBCMessageStore() + { + _stateManager = new StateManager(_eventManager); + } + + @Override + public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler configRecoveryHandler) throws Exception + { + _stateManager.attainState(State.INITIALISING); + _configRecoveryHandler = configRecoveryHandler; + _virtualHost = virtualHost; + + } + + @Override + public void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler recoveryHandler, + TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception + { + if(_stateManager.isInState(State.INITIAL)) + { + _stateManager.attainState(State.INITIALISING); + } + + _virtualHost = virtualHost; + _tlogRecoveryHandler = tlogRecoveryHandler; + _messageRecoveryHandler = recoveryHandler; + + completeInitialisation(); + } + + private void completeInitialisation() throws ClassNotFoundException, SQLException, AMQStoreException + { + commonConfiguration(); + + _stateManager.attainState(State.INITIALISED); + } + + @Override + public void activate() throws Exception + { + if(_stateManager.isInState(State.INITIALISING)) + { + completeInitialisation(); + } + _stateManager.attainState(State.ACTIVATING); + + // this recovers durable exchanges, queues, and bindings + if(_configRecoveryHandler != null) + { + recoverConfiguration(_configRecoveryHandler); + } + if(_messageRecoveryHandler != null) + { + recoverMessages(_messageRecoveryHandler); + } + if(_tlogRecoveryHandler != null) + { + TransactionLogRecoveryHandler.DtxRecordRecoveryHandler dtxrh = recoverQueueEntries(_tlogRecoveryHandler); + recoverXids(dtxrh); + + } + + _stateManager.attainState(State.ACTIVE); + } + + private void commonConfiguration() + throws ClassNotFoundException, SQLException, AMQStoreException + { + implementationSpecificConfiguration(_virtualHost.getName(), _virtualHost); + createOrOpenDatabase(); + upgradeIfNecessary(); + } + + protected void upgradeIfNecessary() throws SQLException, AMQStoreException + { + Connection conn = newAutoCommitConnection(); + try + { + + PreparedStatement statement = conn.prepareStatement(SELECT_FROM_DB_VERSION); + try + { + ResultSet rs = statement.executeQuery(); + try + { + if(!rs.next()) + { + throw new AMQStoreException(DB_VERSION_TABLE_NAME + " does not contain the database version"); + } + int version = rs.getInt(1); + switch (version) + { + case 6: + upgradeFromV6(); + case DB_VERSION: + return; + default: + throw new AMQStoreException("Unknown database version: " + version); + } + } + finally + { + rs.close(); + } + } + finally + { + statement.close(); + } + } + finally + { + conn.close(); + } + + } + + private void upgradeFromV6() throws SQLException + { + updateDbVersion(7); + } + + private void updateDbVersion(int newVersion) throws SQLException + { + Connection conn = newAutoCommitConnection(); + try + { + + PreparedStatement statement = conn.prepareStatement(UPDATE_DB_VERSION); + try + { + statement.setInt(1,newVersion); + statement.execute(); + } + finally + { + statement.close(); + } + } + finally + { + conn.close(); + } + } + + protected abstract void implementationSpecificConfiguration(String name, + VirtualHost virtualHost) throws ClassNotFoundException, SQLException; + + abstract protected Logger getLogger(); + + abstract protected String getSqlBlobType(); + + abstract protected String getSqlVarBinaryType(int size); + + abstract protected String getSqlBigIntType(); + + protected void createOrOpenDatabase() throws SQLException + { + Connection conn = newAutoCommitConnection(); + + createVersionTable(conn); + createConfigVersionTable(conn); + createConfiguredObjectsTable(conn); + createQueueEntryTable(conn); + createMetaDataTable(conn); + createMessageContentTable(conn); + createLinkTable(conn); + createBridgeTable(conn); + createXidTable(conn); + createXidActionTable(conn); + conn.close(); + } + + private void createVersionTable(final Connection conn) throws SQLException + { + if(!tableExists(DB_VERSION_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute(CREATE_DB_VERSION_TABLE); + } + finally + { + stmt.close(); + } + + PreparedStatement pstmt = conn.prepareStatement(INSERT_INTO_DB_VERSION); + try + { + pstmt.setInt(1, DB_VERSION); + pstmt.execute(); + } + finally + { + pstmt.close(); + } + } + } + + private void createConfigVersionTable(final Connection conn) throws SQLException + { + if(!tableExists(CONFIGURATION_VERSION_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute(CREATE_CONFIG_VERSION_TABLE); + } + finally + { + stmt.close(); + } + + PreparedStatement pstmt = conn.prepareStatement(INSERT_INTO_CONFIG_VERSION); + try + { + pstmt.setInt(1, DEFAULT_CONFIG_VERSION); + pstmt.execute(); + } + finally + { + pstmt.close(); + } + } + } + + private void createConfiguredObjectsTable(final Connection conn) throws SQLException + { + if(!tableExists(CONFIGURED_OBJECTS_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute("CREATE TABLE " + CONFIGURED_OBJECTS_TABLE_NAME + + " ( id VARCHAR(36) not null, object_type varchar(255), attributes "+getSqlBlobType()+", PRIMARY KEY (id))"); + } + finally + { + stmt.close(); + } + } + } + + + + private void createQueueEntryTable(final Connection conn) throws SQLException + { + if(!tableExists(QUEUE_ENTRY_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute("CREATE TABLE "+ QUEUE_ENTRY_TABLE_NAME +" ( queue_id varchar(36) not null, message_id " + + getSqlBigIntType() + " not null, PRIMARY KEY (queue_id, message_id) )"); + } + finally + { + stmt.close(); + } + } + + } + + private void createMetaDataTable(final Connection conn) throws SQLException + { + if(!tableExists(META_DATA_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute("CREATE TABLE " + + META_DATA_TABLE_NAME + + " ( message_id " + + getSqlBigIntType() + + " not null, meta_data " + + getSqlBlobType() + + ", PRIMARY KEY ( message_id ) )"); + } + finally + { + stmt.close(); + } + } + + } + + private void createMessageContentTable(final Connection conn) throws SQLException + { + if(!tableExists(MESSAGE_CONTENT_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute("CREATE TABLE " + + MESSAGE_CONTENT_TABLE_NAME + + " ( message_id " + + getSqlBigIntType() + + " not null, content " + + getSqlBlobType() + + ", PRIMARY KEY (message_id) )"); + } + finally + { + stmt.close(); + } + } + + } + + private void createLinkTable(final Connection conn) throws SQLException + { + if(!tableExists(LINKS_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute("CREATE TABLE "+ LINKS_TABLE_NAME +" ( id_lsb " + getSqlBigIntType() + " not null," + + " id_msb " + getSqlBigIntType() + " not null," + + " create_time " + getSqlBigIntType() + " not null," + + " arguments "+getSqlBlobType()+", PRIMARY KEY ( id_lsb, id_msb ))"); + } + finally + { + stmt.close(); + } + } + } + + private void createBridgeTable(final Connection conn) throws SQLException + { + if(!tableExists(BRIDGES_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute("CREATE TABLE "+ BRIDGES_TABLE_NAME +" ( id_lsb " + getSqlBigIntType() + " not null," + + " id_msb " + getSqlBigIntType() + " not null," + + " create_time " + getSqlBigIntType() + " not null," + + " link_id_lsb " + getSqlBigIntType() + " not null," + + " link_id_msb " + getSqlBigIntType() + " not null," + + " arguments "+getSqlBlobType()+", PRIMARY KEY ( id_lsb, id_msb ))"); + } + finally + { + stmt.close(); + } + } + } + + private void createXidTable(final Connection conn) throws SQLException + { + if(!tableExists(XID_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute("CREATE TABLE " + + XID_TABLE_NAME + + " ( format " + getSqlBigIntType() + " not null," + + " global_id " + + getSqlVarBinaryType(64) + + ", branch_id " + + getSqlVarBinaryType(64) + + " , PRIMARY KEY ( format, " + + + "global_id, branch_id ))"); + } + finally + { + stmt.close(); + } + } + } + + private void createXidActionTable(final Connection conn) throws SQLException + { + if(!tableExists(XID_ACTIONS_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute("CREATE TABLE " + XID_ACTIONS_TABLE_NAME + " ( format " + getSqlBigIntType() + " not null," + + " global_id " + getSqlVarBinaryType(64) + " not null, branch_id " + getSqlVarBinaryType( + 64) + " not null, " + + "action_type char not null, queue_id varchar(36) not null, message_id " + getSqlBigIntType() + " not null" + + ", PRIMARY KEY ( " + + "format, global_id, branch_id, action_type, queue_id, message_id))"); + } + finally + { + stmt.close(); + } + } + } + + protected boolean tableExists(final String tableName, final Connection conn) throws SQLException + { + DatabaseMetaData metaData = conn.getMetaData(); + ResultSet rs = metaData.getTables(null, null, "%", null); + + try + { + + while(rs.next()) + { + final String table = rs.getString(3); + if(tableName.equalsIgnoreCase(table)) + { + return true; + } + } + return false; + } + finally + { + rs.close(); + } + } + + protected void recoverConfiguration(ConfigurationRecoveryHandler recoveryHandler) throws AMQException + { + try + { + recoveryHandler.beginConfigurationRecovery(this, getConfigVersion()); + loadConfiguredObjects(recoveryHandler); + + setConfigVersion(recoveryHandler.completeConfigurationRecovery()); + } + catch (SQLException e) + { + throw new AMQStoreException("Error recovering persistent state: " + e.getMessage(), e); + } + } + + private void setConfigVersion(int version) throws SQLException + { + Connection conn = newAutoCommitConnection(); + try + { + + PreparedStatement stmt = conn.prepareStatement(UPDATE_CONFIG_VERSION); + try + { + stmt.setInt(1, version); + stmt.execute(); + + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + + private int getConfigVersion() throws SQLException + { + Connection conn = newAutoCommitConnection(); + try + { + + Statement stmt = conn.createStatement(); + try + { + ResultSet rs = stmt.executeQuery(SELECT_FROM_CONFIG_VERSION); + try + { + + if(rs.next()) + { + return rs.getInt(1); + } + return DEFAULT_CONFIG_VERSION; + } + finally + { + rs.close(); + } + + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + + } + + @Override + public void close() throws Exception + { + _closed.getAndSet(true); + _stateManager.attainState(State.CLOSING); + + doClose(); + + _stateManager.attainState(State.CLOSED); + } + + + protected abstract void doClose() throws Exception; + + @Override + public StoredMessage addMessage(StorableMessageMetaData metaData) + { + if(metaData.isPersistent()) + { + return new StoredJDBCMessage(_messageId.incrementAndGet(), metaData); + } + else + { + return new StoredMemoryMessage(_messageId.incrementAndGet(), metaData); + } + } + + public StoredMessage getMessage(long messageNumber) + { + return null; + } + + public void removeMessage(long messageId) + { + try + { + Connection conn = newConnection(); + try + { + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_META_DATA); + try + { + stmt.setLong(1,messageId); + int results = stmt.executeUpdate(); + stmt.close(); + + if (results == 0) + { + getLogger().warn("Message metadata not found for message id " + messageId); + } + + if (getLogger().isDebugEnabled()) + { + getLogger().debug("Deleted metadata for message " + messageId); + } + + stmt = conn.prepareStatement(DELETE_FROM_MESSAGE_CONTENT); + stmt.setLong(1,messageId); + results = stmt.executeUpdate(); + } + finally + { + stmt.close(); + } + conn.commit(); + } + catch(SQLException e) + { + try + { + conn.rollback(); + } + catch(SQLException t) + { + // ignore - we are re-throwing underlying exception + } + + throw e; + + } + finally + { + conn.close(); + } + } + catch (SQLException e) + { + throw new RuntimeException("Error removing message with id " + messageId + " from database: " + e.getMessage(), e); + } + + } + + + @Override + public void create(UUID id, String type, Map attributes) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + insertConfiguredObject(new ConfiguredObjectRecord(id, type, attributes)); + } + + } + + @Override + public void remove(UUID id, String type) throws AMQStoreException + { + int results = removeConfiguredObject(id); + if (results == 0) + { + throw new AMQStoreException(type + " with id " + id + " not found"); + } + } + + @Override + public void update(UUID id, String type, Map attributes) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + ConfiguredObjectRecord queueConfiguredObject = loadConfiguredObject(id); + if (queueConfiguredObject != null) + { + ConfiguredObjectRecord newQueueRecord = new ConfiguredObjectRecord(id, type, attributes); + updateConfiguredObject(newQueueRecord); + } + } + + } + + /** + * Convenience method to create a new Connection configured for TRANSACTION_READ_COMMITED + * isolation and with auto-commit transactions enabled. + */ + protected Connection newAutoCommitConnection() throws SQLException + { + final Connection connection = newConnection(); + try + { + connection.setAutoCommit(true); + } + catch (SQLException sqlEx) + { + + try + { + connection.close(); + } + finally + { + throw sqlEx; + } + } + + return connection; + } + + /** + * Convenience method to create a new Connection configured for TRANSACTION_READ_COMMITED + * isolation and with auto-commit transactions disabled. + */ + protected Connection newConnection() throws SQLException + { + final Connection connection = getConnection(); + try + { + connection.setAutoCommit(false); + connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + } + catch (SQLException sqlEx) + { + try + { + connection.close(); + } + finally + { + throw sqlEx; + } + } + return connection; + } + + protected abstract Connection getConnection() throws SQLException; + + private byte[] convertStringMapToBytes(final Map arguments) throws AMQStoreException + { + byte[] argumentBytes; + if(arguments == null) + { + argumentBytes = new byte[0]; + } + else + { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(bos); + + + try + { + dos.writeInt(arguments.size()); + for(Map.Entry arg : arguments.entrySet()) + { + dos.writeUTF(arg.getKey()); + dos.writeUTF(arg.getValue()); + } + } + catch (IOException e) + { + // This should never happen + throw new AMQStoreException(e.getMessage(), e); + } + argumentBytes = bos.toByteArray(); + } + return argumentBytes; + } + + @Override + public Transaction newTransaction() + { + return new JDBCTransaction(); + } + + public void enqueueMessage(ConnectionWrapper connWrapper, final TransactionLogResource queue, Long messageId) throws AMQStoreException + { + Connection conn = connWrapper.getConnection(); + + + try + { + if (getLogger().isDebugEnabled()) + { + getLogger().debug("Enqueuing message " + + messageId + + " on queue " + + (queue instanceof AMQQueue + ? ((AMQQueue) queue).getName() + : "") + + queue.getId() + + "[Connection" + + conn + + "]"); + } + + PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_QUEUE_ENTRY); + try + { + stmt.setString(1, queue.getId().toString()); + stmt.setLong(2,messageId); + stmt.executeUpdate(); + } + finally + { + stmt.close(); + } + + } + catch (SQLException e) + { + getLogger().error("Failed to enqueue: " + e.getMessage(), e); + throw new AMQStoreException("Error writing enqueued message with id " + messageId + " for queue " + (queue instanceof AMQQueue ? ((AMQQueue)queue).getName() : "" ) + " with id " + queue.getId() + + " to database", e); + } + + } + + public void dequeueMessage(ConnectionWrapper connWrapper, final TransactionLogResource queue, Long messageId) throws AMQStoreException + { + + Connection conn = connWrapper.getConnection(); + + + try + { + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_QUEUE_ENTRY); + try + { + stmt.setString(1, queue.getId().toString()); + stmt.setLong(2, messageId); + int results = stmt.executeUpdate(); + + + + if(results != 1) + { + throw new AMQStoreException("Unable to find message with id " + messageId + " on queue " + (queue instanceof AMQQueue ? ((AMQQueue)queue).getName() : "" ) + + " with id " + queue.getId()); + } + + if (getLogger().isDebugEnabled()) + { + getLogger().debug("Dequeuing message " + messageId + " on queue " + (queue instanceof AMQQueue + ? ((AMQQueue) queue).getName() + : "") + + " with id " + queue.getId()); + } + } + finally + { + stmt.close(); + } + + } + catch (SQLException e) + { + getLogger().error("Failed to dequeue: " + e.getMessage(), e); + throw new AMQStoreException("Error deleting enqueued message with id " + messageId + " for queue " + (queue instanceof AMQQueue ? ((AMQQueue)queue).getName() : "" ) + + " with id " + queue.getId() + " from database", e); + } + + } + + private void removeXid(ConnectionWrapper connWrapper, long format, byte[] globalId, byte[] branchId) + throws AMQStoreException + { + Connection conn = connWrapper.getConnection(); + + + try + { + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_XIDS); + try + { + stmt.setLong(1,format); + stmt.setBytes(2,globalId); + stmt.setBytes(3,branchId); + int results = stmt.executeUpdate(); + + + + if(results != 1) + { + throw new AMQStoreException("Unable to find message with xid"); + } + } + finally + { + stmt.close(); + } + + stmt = conn.prepareStatement(DELETE_FROM_XID_ACTIONS); + try + { + stmt.setLong(1,format); + stmt.setBytes(2,globalId); + stmt.setBytes(3,branchId); + int results = stmt.executeUpdate(); + + } + finally + { + stmt.close(); + } + + } + catch (SQLException e) + { + getLogger().error("Failed to dequeue: " + e.getMessage(), e); + throw new AMQStoreException("Error deleting enqueued message with xid", e); + } + + } + + private void recordXid(ConnectionWrapper connWrapper, long format, byte[] globalId, byte[] branchId, + Transaction.Record[] enqueues, Transaction.Record[] dequeues) throws AMQStoreException + { + Connection conn = connWrapper.getConnection(); + + + try + { + + PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_XIDS); + try + { + stmt.setLong(1,format); + stmt.setBytes(2, globalId); + stmt.setBytes(3, branchId); + stmt.executeUpdate(); + } + finally + { + stmt.close(); + } + + stmt = conn.prepareStatement(INSERT_INTO_XID_ACTIONS); + + try + { + stmt.setLong(1,format); + stmt.setBytes(2, globalId); + stmt.setBytes(3, branchId); + + if(enqueues != null) + { + stmt.setString(4, "E"); + for(Transaction.Record record : enqueues) + { + stmt.setString(5, record.getQueue().getId().toString()); + stmt.setLong(6, record.getMessage().getMessageNumber()); + stmt.executeUpdate(); + } + } + + if(dequeues != null) + { + stmt.setString(4, "D"); + for(Transaction.Record record : dequeues) + { + stmt.setString(5, record.getQueue().getId().toString()); + stmt.setLong(6, record.getMessage().getMessageNumber()); + stmt.executeUpdate(); + } + } + + } + finally + { + stmt.close(); + } + + } + catch (SQLException e) + { + getLogger().error("Failed to enqueue: " + e.getMessage(), e); + throw new AMQStoreException("Error writing xid ", e); + } + + } + + protected boolean isConfigStoreOnly() + { + return _messageRecoveryHandler == null; + } + + private static final class ConnectionWrapper + { + private final Connection _connection; + + public ConnectionWrapper(Connection conn) + { + _connection = conn; + } + + public Connection getConnection() + { + return _connection; + } + } + + + public void commitTran(ConnectionWrapper connWrapper) throws AMQStoreException + { + + try + { + Connection conn = connWrapper.getConnection(); + conn.commit(); + + if (getLogger().isDebugEnabled()) + { + getLogger().debug("commit tran completed"); + } + + conn.close(); + } + catch (SQLException e) + { + throw new AMQStoreException("Error commit tx: " + e.getMessage(), e); + } + finally + { + + } + } + + public StoreFuture commitTranAsync(ConnectionWrapper connWrapper) throws AMQStoreException + { + commitTran(connWrapper); + return StoreFuture.IMMEDIATE_FUTURE; + } + + public void abortTran(ConnectionWrapper connWrapper) throws AMQStoreException + { + if (connWrapper == null) + { + throw new AMQStoreException("Fatal internal error: transactional context is empty at abortTran"); + } + + if (getLogger().isDebugEnabled()) + { + getLogger().debug("abort tran called: " + connWrapper.getConnection()); + } + + try + { + Connection conn = connWrapper.getConnection(); + conn.rollback(); + conn.close(); + } + catch (SQLException e) + { + throw new AMQStoreException("Error aborting transaction: " + e.getMessage(), e); + } + + } + + public Long getNewMessageId() + { + return _messageId.incrementAndGet(); + } + + private void storeMetaData(Connection conn, long messageId, StorableMessageMetaData metaData) + throws SQLException + { + if(getLogger().isDebugEnabled()) + { + getLogger().debug("Adding metadata for message " + messageId); + } + + PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_META_DATA); + try + { + stmt.setLong(1,messageId); + + final int bodySize = 1 + metaData.getStorableSize(); + byte[] underlying = new byte[bodySize]; + underlying[0] = (byte) metaData.getType().ordinal(); + ByteBuffer buf = ByteBuffer.wrap(underlying); + buf.position(1); + buf = buf.slice(); + + metaData.writeToBuffer(0, buf); + ByteArrayInputStream bis = new ByteArrayInputStream(underlying); + try + { + stmt.setBinaryStream(2,bis,underlying.length); + int result = stmt.executeUpdate(); + + if(result == 0) + { + throw new RuntimeException("Unable to add meta data for message " +messageId); + } + } + finally + { + try + { + bis.close(); + } + catch (IOException e) + { + + throw new SQLException(e); + } + } + + } + finally + { + stmt.close(); + } + + } + + protected void recoverMessages(MessageStoreRecoveryHandler recoveryHandler) throws SQLException + { + Connection conn = newAutoCommitConnection(); + try + { + MessageStoreRecoveryHandler.StoredMessageRecoveryHandler messageHandler = recoveryHandler.begin(); + + Statement stmt = conn.createStatement(); + try + { + ResultSet rs = stmt.executeQuery(SELECT_ALL_FROM_META_DATA); + try + { + + long maxId = 0; + + while(rs.next()) + { + + long messageId = rs.getLong(1); + if(messageId > maxId) + { + maxId = messageId; + } + + byte[] dataAsBytes = getBlobAsBytes(rs, 2); + + ByteBuffer buf = ByteBuffer.wrap(dataAsBytes); + buf.position(1); + buf = buf.slice(); + MessageMetaDataType type = MessageMetaDataTypeRegistry.fromOrdinal(dataAsBytes[0]); + StorableMessageMetaData metaData = type.createMetaData(buf); + StoredJDBCMessage message = new StoredJDBCMessage(messageId, metaData, true); + messageHandler.message(message); + } + + _messageId.set(maxId); + + messageHandler.completeMessageRecovery(); + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + + + protected TransactionLogRecoveryHandler.DtxRecordRecoveryHandler recoverQueueEntries(TransactionLogRecoveryHandler recoveryHandler) throws SQLException + { + Connection conn = newAutoCommitConnection(); + try + { + TransactionLogRecoveryHandler.QueueEntryRecoveryHandler queueEntryHandler = recoveryHandler.begin(this); + + Statement stmt = conn.createStatement(); + try + { + ResultSet rs = stmt.executeQuery(SELECT_FROM_QUEUE_ENTRY); + try + { + while(rs.next()) + { + + String id = rs.getString(1); + long messageId = rs.getLong(2); + queueEntryHandler.queueEntry(UUID.fromString(id), messageId); + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + + return queueEntryHandler.completeQueueEntryRecovery(); + } + finally + { + conn.close(); + } + } + + private static final class Xid + { + + private final long _format; + private final byte[] _globalId; + private final byte[] _branchId; + + public Xid(long format, byte[] globalId, byte[] branchId) + { + _format = format; + _globalId = globalId; + _branchId = branchId; + } + + public long getFormat() + { + return _format; + } + + public byte[] getGlobalId() + { + return _globalId; + } + + public byte[] getBranchId() + { + return _branchId; + } + } + + private static class RecordImpl implements Transaction.Record, TransactionLogResource, EnqueableMessage + { + + private long _messageNumber; + private UUID _queueId; + + public RecordImpl(UUID queueId, long messageNumber) + { + _messageNumber = messageNumber; + _queueId = queueId; + } + + @Override + public TransactionLogResource getQueue() + { + return this; + } + + @Override + public EnqueableMessage getMessage() + { + return this; + } + + @Override + public long getMessageNumber() + { + return _messageNumber; + } + + @Override + public boolean isPersistent() + { + return true; + } + + @Override + public StoredMessage getStoredMessage() + { + throw new UnsupportedOperationException(); + } + + @Override + public UUID getId() + { + return _queueId; + } + } + + protected void recoverXids(TransactionLogRecoveryHandler.DtxRecordRecoveryHandler dtxrh) throws SQLException + { + Connection conn = newAutoCommitConnection(); + try + { + List xids = new ArrayList(); + + Statement stmt = conn.createStatement(); + try + { + ResultSet rs = stmt.executeQuery(SELECT_ALL_FROM_XIDS); + try + { + while(rs.next()) + { + + long format = rs.getLong(1); + byte[] globalId = rs.getBytes(2); + byte[] branchId = rs.getBytes(3); + xids.add(new Xid(format, globalId, branchId)); + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + + + + for(Xid xid : xids) + { + List enqueues = new ArrayList(); + List dequeues = new ArrayList(); + + PreparedStatement pstmt = conn.prepareStatement(SELECT_ALL_FROM_XID_ACTIONS); + + try + { + pstmt.setLong(1, xid.getFormat()); + pstmt.setBytes(2, xid.getGlobalId()); + pstmt.setBytes(3, xid.getBranchId()); + + ResultSet rs = pstmt.executeQuery(); + try + { + while(rs.next()) + { + + String actionType = rs.getString(1); + UUID queueId = UUID.fromString(rs.getString(2)); + long messageId = rs.getLong(3); + + RecordImpl record = new RecordImpl(queueId, messageId); + List records = "E".equals(actionType) ? enqueues : dequeues; + records.add(record); + } + } + finally + { + rs.close(); + } + } + finally + { + pstmt.close(); + } + + dtxrh.dtxRecord(xid.getFormat(), xid.getGlobalId(), xid.getBranchId(), + enqueues.toArray(new RecordImpl[enqueues.size()]), + dequeues.toArray(new RecordImpl[dequeues.size()])); + } + + + dtxrh.completeDtxRecordRecovery(); + } + finally + { + conn.close(); + } + + } + + StorableMessageMetaData getMetaData(long messageId) throws SQLException + { + + Connection conn = newAutoCommitConnection(); + try + { + PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_META_DATA); + try + { + stmt.setLong(1,messageId); + ResultSet rs = stmt.executeQuery(); + try + { + + if(rs.next()) + { + byte[] dataAsBytes = getBlobAsBytes(rs, 1); + ByteBuffer buf = ByteBuffer.wrap(dataAsBytes); + buf.position(1); + buf = buf.slice(); + MessageMetaDataType type = MessageMetaDataTypeRegistry.fromOrdinal(dataAsBytes[0]); + StorableMessageMetaData metaData = type.createMetaData(buf); + + return metaData; + } + else + { + throw new RuntimeException("Meta data not found for message with id " + messageId); + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + + protected abstract byte[] getBlobAsBytes(ResultSet rs, int col) throws SQLException; + + private void addContent(Connection conn, long messageId, ByteBuffer src) + { + if(getLogger().isDebugEnabled()) + { + getLogger().debug("Adding content for message " + messageId); + } + PreparedStatement stmt = null; + + try + { + src = src.slice(); + + byte[] chunkData = new byte[src.limit()]; + src.duplicate().get(chunkData); + + stmt = conn.prepareStatement(INSERT_INTO_MESSAGE_CONTENT); + stmt.setLong(1,messageId); + + ByteArrayInputStream bis = new ByteArrayInputStream(chunkData); + stmt.setBinaryStream(2, bis, chunkData.length); + stmt.executeUpdate(); + } + catch (SQLException e) + { + closeConnection(conn); + throw new RuntimeException("Error adding content for message " + messageId + ": " + e.getMessage(), e); + } + finally + { + closePreparedStatement(stmt); + } + + } + + public int getContent(long messageId, int offset, ByteBuffer dst) + { + Connection conn = null; + PreparedStatement stmt = null; + + try + { + conn = newAutoCommitConnection(); + + stmt = conn.prepareStatement(SELECT_FROM_MESSAGE_CONTENT); + stmt.setLong(1,messageId); + ResultSet rs = stmt.executeQuery(); + + int written = 0; + + if (rs.next()) + { + + byte[] dataAsBytes = getBlobAsBytes(rs, 1); + int size = dataAsBytes.length; + + if (offset > size) + { + throw new RuntimeException("Offset " + offset + " is greater than message size " + size + + " for message id " + messageId + "!"); + + } + + written = size - offset; + if(written > dst.remaining()) + { + written = dst.remaining(); + } + + dst.put(dataAsBytes, offset, written); + } + + return written; + + } + catch (SQLException e) + { + throw new RuntimeException("Error retrieving content from offset " + offset + " for message " + messageId + ": " + e.getMessage(), e); + } + finally + { + closePreparedStatement(stmt); + closeConnection(conn); + } + + + } + + @Override + public boolean isPersistent() + { + return true; + } + + + protected class JDBCTransaction implements Transaction + { + private final ConnectionWrapper _connWrapper; + private int _storeSizeIncrease; + + + protected JDBCTransaction() + { + try + { + _connWrapper = new ConnectionWrapper(newConnection()); + } + catch (SQLException e) + { + throw new RuntimeException(e); + } + } + + @Override + public void enqueueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException + { + final StoredMessage storedMessage = message.getStoredMessage(); + if(storedMessage instanceof StoredJDBCMessage) + { + try + { + ((StoredJDBCMessage) storedMessage).store(_connWrapper.getConnection()); + } + catch (SQLException e) + { + throw new AMQStoreException("Exception on enqueuing message " + _messageId, e); + } + } + _storeSizeIncrease += storedMessage.getMetaData().getContentSize(); + AbstractJDBCMessageStore.this.enqueueMessage(_connWrapper, queue, message.getMessageNumber()); + } + + @Override + public void dequeueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException + { + AbstractJDBCMessageStore.this.dequeueMessage(_connWrapper, queue, message.getMessageNumber()); + + } + + @Override + public void commitTran() throws AMQStoreException + { + AbstractJDBCMessageStore.this.commitTran(_connWrapper); + storedSizeChange(_storeSizeIncrease); + } + + @Override + public StoreFuture commitTranAsync() throws AMQStoreException + { + final StoreFuture storeFuture = AbstractJDBCMessageStore.this.commitTranAsync(_connWrapper); + storedSizeChange(_storeSizeIncrease); + return storeFuture; + } + + @Override + public void abortTran() throws AMQStoreException + { + AbstractJDBCMessageStore.this.abortTran(_connWrapper); + } + + @Override + public void removeXid(long format, byte[] globalId, byte[] branchId) throws AMQStoreException + { + AbstractJDBCMessageStore.this.removeXid(_connWrapper, format, globalId, branchId); + } + + @Override + public void recordXid(long format, byte[] globalId, byte[] branchId, Record[] enqueues, Record[] dequeues) + throws AMQStoreException + { + AbstractJDBCMessageStore.this.recordXid(_connWrapper, format, globalId, branchId, enqueues, dequeues); + } + } + + private class StoredJDBCMessage implements StoredMessage + { + + private final long _messageId; + private final boolean _isRecovered; + + private StorableMessageMetaData _metaData; + private volatile SoftReference _metaDataRef; + private byte[] _data; + private volatile SoftReference _dataRef; + + + StoredJDBCMessage(long messageId, StorableMessageMetaData metaData) + { + this(messageId, metaData, false); + } + + + StoredJDBCMessage(long messageId, + StorableMessageMetaData metaData, boolean isRecovered) + { + _messageId = messageId; + _isRecovered = isRecovered; + + if(!_isRecovered) + { + _metaData = metaData; + } + _metaDataRef = new SoftReference(metaData); + } + + @Override + public StorableMessageMetaData getMetaData() + { + StorableMessageMetaData metaData = _metaData == null ? _metaDataRef.get() : _metaData; + if(metaData == null) + { + try + { + metaData = AbstractJDBCMessageStore.this.getMetaData(_messageId); + } + catch (SQLException e) + { + throw new RuntimeException(e); + } + _metaDataRef = new SoftReference(metaData); + } + + return metaData; + } + + @Override + public long getMessageNumber() + { + return _messageId; + } + + @Override + public void addContent(int offsetInMessage, ByteBuffer src) + { + src = src.slice(); + + if(_data == null) + { + _data = new byte[src.remaining()]; + _dataRef = new SoftReference(_data); + src.duplicate().get(_data); + } + else + { + byte[] oldData = _data; + _data = new byte[oldData.length + src.remaining()]; + _dataRef = new SoftReference(_data); + + System.arraycopy(oldData,0,_data,0,oldData.length); + src.duplicate().get(_data, oldData.length, src.remaining()); + } + + } + + @Override + public int getContent(int offsetInMessage, ByteBuffer dst) + { + byte[] data = _dataRef == null ? null : _dataRef.get(); + if(data != null) + { + int length = Math.min(dst.remaining(), data.length - offsetInMessage); + dst.put(data, offsetInMessage, length); + return length; + } + else + { + return AbstractJDBCMessageStore.this.getContent(_messageId, offsetInMessage, dst); + } + } + + + @Override + public ByteBuffer getContent(int offsetInMessage, int size) + { + ByteBuffer buf = ByteBuffer.allocate(size); + int length = getContent(offsetInMessage, buf); + buf.position(0); + buf.limit(length); + return buf; + } + + @Override + public synchronized StoreFuture flushToStore() + { + Connection conn = null; + try + { + if(!stored()) + { + conn = newConnection(); + + store(conn); + + conn.commit(); + storedSizeChange(getMetaData().getContentSize()); + } + } + catch (SQLException e) + { + if(getLogger().isDebugEnabled()) + { + getLogger().debug("Error when trying to flush message " + _messageId + " to store: " + e); + } + throw new RuntimeException(e); + } + finally + { + closeConnection(conn); + } + return StoreFuture.IMMEDIATE_FUTURE; + } + + @Override + public void remove() + { + int delta = getMetaData().getContentSize(); + AbstractJDBCMessageStore.this.removeMessage(_messageId); + storedSizeChange(-delta); + } + + private synchronized void store(final Connection conn) throws SQLException + { + if (!stored()) + { + try + { + storeMetaData(conn, _messageId, _metaData); + AbstractJDBCMessageStore.this.addContent(conn, _messageId, + _data == null ? ByteBuffer.allocate(0) : ByteBuffer.wrap(_data)); + } + finally + { + _metaData = null; + _data = null; + } + + if(getLogger().isDebugEnabled()) + { + getLogger().debug("Storing message " + _messageId + " to store"); + } + } + } + + private boolean stored() + { + return _metaData == null || _isRecovered; + } + } + + protected void closeConnection(final Connection conn) + { + if(conn != null) + { + try + { + conn.close(); + } + catch (SQLException e) + { + getLogger().error("Problem closing connection", e); + } + } + } + + protected void closePreparedStatement(final PreparedStatement stmt) + { + if (stmt != null) + { + try + { + stmt.close(); + } + catch(SQLException e) + { + getLogger().error("Problem closing prepared statement", e); + } + } + } + + @Override + public void addEventListener(EventListener eventListener, Event... events) + { + _eventManager.addEventListener(eventListener, events); + } + + private void insertConfiguredObject(ConfiguredObjectRecord configuredObject) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + try + { + Connection conn = newAutoCommitConnection(); + try + { + PreparedStatement stmt = conn.prepareStatement(FIND_CONFIGURED_OBJECT); + try + { + stmt.setString(1, configuredObject.getId().toString()); + ResultSet rs = stmt.executeQuery(); + try + { + // If we don't have any data in the result set then we can add this configured object + if (!rs.next()) + { + PreparedStatement insertStmt = conn.prepareStatement(INSERT_INTO_CONFIGURED_OBJECTS); + try + { + insertStmt.setString(1, configuredObject.getId().toString()); + insertStmt.setString(2, configuredObject.getType()); + if(configuredObject.getAttributes() == null) + { + insertStmt.setNull(3, Types.BLOB); + } + else + { + final Map attributes = configuredObject.getAttributes(); + byte[] attributesAsBytes = new ObjectMapper().writeValueAsBytes(attributes); + ByteArrayInputStream bis = new ByteArrayInputStream(attributesAsBytes); + insertStmt.setBinaryStream(3, bis, attributesAsBytes.length); + } + insertStmt.execute(); + } + finally + { + insertStmt.close(); + } + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + catch (JsonMappingException e) + { + throw new AMQStoreException("Error inserting of configured object " + configuredObject + " into database: " + e.getMessage(), e); + } + catch (JsonGenerationException e) + { + throw new AMQStoreException("Error inserting of configured object " + configuredObject + " into database: " + e.getMessage(), e); + } + catch (IOException e) + { + throw new AMQStoreException("Error inserting of configured object " + configuredObject + " into database: " + e.getMessage(), e); + } + catch (SQLException e) + { + throw new AMQStoreException("Error inserting of configured object " + configuredObject + " into database: " + e.getMessage(), e); + } + } + } + + private int removeConfiguredObject(UUID id) throws AMQStoreException + { + int results = 0; + try + { + Connection conn = newAutoCommitConnection(); + try + { + results = removeConfiguredObject(id, conn); + } + finally + { + conn.close(); + } + } + catch (SQLException e) + { + throw new AMQStoreException("Error deleting of configured object with id " + id + " from database: " + e.getMessage(), e); + } + return results; + } + + public UUID[] removeConfiguredObjects(UUID... objects) throws AMQStoreException + { + Collection removed = new ArrayList(objects.length); + try + { + + Connection conn = newAutoCommitConnection(); + try + { + for(UUID id : objects) + { + if(removeConfiguredObject(id, conn) != 0) + { + removed.add(id); + } + } + } + finally + { + conn.close(); + } + } + catch (SQLException e) + { + throw new AMQStoreException("Error deleting of configured objects " + Arrays.asList(objects) + " from database: " + e.getMessage(), e); + } + return removed.toArray(new UUID[removed.size()]); + } + + private int removeConfiguredObject(final UUID id, final Connection conn) throws SQLException + { + final int results;PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_CONFIGURED_OBJECTS); + try + { + stmt.setString(1, id.toString()); + results = stmt.executeUpdate(); + } + finally + { + stmt.close(); + } + return results; + } + + private void updateConfiguredObject(final ConfiguredObjectRecord configuredObject) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE)) + { + try + { + Connection conn = newAutoCommitConnection(); + try + { + updateConfiguredObject(configuredObject, false, conn); + } + finally + { + conn.close(); + } + } + catch (SQLException e) + { + throw new AMQStoreException("Error updating configured object " + configuredObject + " in database: " + e.getMessage(), e); + } + } + } + + @Override + public void update(ConfiguredObjectRecord... records) throws AMQStoreException + { + update(false, records); + } + + public void update(boolean createIfNecessary, ConfiguredObjectRecord... records) throws AMQStoreException + { + if (_stateManager.isInState(State.ACTIVE) || _stateManager.isInState(State.ACTIVATING)) + { + try + { + Connection conn = newConnection(); + try + { + for(ConfiguredObjectRecord record : records) + { + updateConfiguredObject(record, createIfNecessary, conn); + } + conn.commit(); + } + finally + { + conn.close(); + } + } + catch (SQLException e) + { + throw new AMQStoreException("Error updating configured objects in database: " + e.getMessage(), e); + } + + } + + } + + private void updateConfiguredObject(ConfiguredObjectRecord configuredObject, + boolean createIfNecessary, + Connection conn) + throws SQLException, AMQStoreException + { + PreparedStatement stmt = conn.prepareStatement(FIND_CONFIGURED_OBJECT); + try + { + stmt.setString(1, configuredObject.getId().toString()); + ResultSet rs = stmt.executeQuery(); + try + { + if (rs.next()) + { + PreparedStatement stmt2 = conn.prepareStatement(UPDATE_CONFIGURED_OBJECTS); + try + { + stmt2.setString(1, configuredObject.getType()); + if (configuredObject.getAttributes() != null) + { + byte[] attributesAsBytes = (new ObjectMapper()).writeValueAsBytes( + configuredObject.getAttributes()); + ByteArrayInputStream bis = new ByteArrayInputStream(attributesAsBytes); + stmt2.setBinaryStream(2, bis, attributesAsBytes.length); + } + else + { + stmt2.setNull(2, Types.BLOB); + } + stmt2.setString(3, configuredObject.getId().toString()); + stmt2.execute(); + } + finally + { + stmt2.close(); + } + } + else if(createIfNecessary) + { + PreparedStatement insertStmt = conn.prepareStatement(INSERT_INTO_CONFIGURED_OBJECTS); + try + { + insertStmt.setString(1, configuredObject.getId().toString()); + insertStmt.setString(2, configuredObject.getType()); + if(configuredObject.getAttributes() == null) + { + insertStmt.setNull(3, Types.BLOB); + } + else + { + final Map attributes = configuredObject.getAttributes(); + byte[] attributesAsBytes = new ObjectMapper().writeValueAsBytes(attributes); + ByteArrayInputStream bis = new ByteArrayInputStream(attributesAsBytes); + insertStmt.setBinaryStream(3, bis, attributesAsBytes.length); + } + insertStmt.execute(); + } + finally + { + insertStmt.close(); + } + } + } + finally + { + rs.close(); + } + } + catch (JsonMappingException e) + { + throw new AMQStoreException("Error updating configured object " + configuredObject + " in database: " + e.getMessage(), e); + } + catch (JsonGenerationException e) + { + throw new AMQStoreException("Error updating configured object " + configuredObject + " in database: " + e.getMessage(), e); + } + catch (IOException e) + { + throw new AMQStoreException("Error updating configured object " + configuredObject + " in database: " + e.getMessage(), e); + } + finally + { + stmt.close(); + } + + } + + private ConfiguredObjectRecord loadConfiguredObject(final UUID id) throws AMQStoreException + { + ConfiguredObjectRecord result = null; + try + { + Connection conn = newAutoCommitConnection(); + try + { + PreparedStatement stmt = conn.prepareStatement(FIND_CONFIGURED_OBJECT); + try + { + stmt.setString(1, id.toString()); + ResultSet rs = stmt.executeQuery(); + try + { + if (rs.next()) + { + String type = rs.getString(1); + String attributes = getBlobAsString(rs, 2); + result = new ConfiguredObjectRecord(id, type, + (new ObjectMapper()).readValue(attributes,Map.class)); + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + catch (JsonMappingException e) + { + throw new AMQStoreException("Error loading of configured object with id " + id + " from database: " + + e.getMessage(), e); + } + catch (JsonParseException e) + { + throw new AMQStoreException("Error loading of configured object with id " + id + " from database: " + + e.getMessage(), e); + } + catch (IOException e) + { + throw new AMQStoreException("Error loading of configured object with id " + id + " from database: " + + e.getMessage(), e); + } + catch (SQLException e) + { + throw new AMQStoreException("Error loading of configured object with id " + id + " from database: " + + e.getMessage(), e); + } + return result; + } + + private void loadConfiguredObjects(ConfigurationRecoveryHandler recoveryHandler) throws SQLException, AMQStoreException + { + Connection conn = newAutoCommitConnection(); + + final ObjectMapper objectMapper = new ObjectMapper(); + try + { + PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_CONFIGURED_OBJECTS); + try + { + ResultSet rs = stmt.executeQuery(); + try + { + while (rs.next()) + { + String id = rs.getString(1); + String objectType = rs.getString(2); + String attributes = getBlobAsString(rs, 3); + recoveryHandler.configuredObject(UUID.fromString(id), objectType, + objectMapper.readValue(attributes,Map.class)); + } + } + catch (JsonMappingException e) + { + throw new AMQStoreException("Error recovering persistent state: " + e.getMessage(), e); + } + catch (JsonParseException e) + { + throw new AMQStoreException("Error recovering persistent state: " + e.getMessage(), e); + } + catch (IOException e) + { + throw new AMQStoreException("Error recovering persistent state: " + e.getMessage(), e); + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + + protected abstract String getBlobAsString(ResultSet rs, int col) throws SQLException; + + protected abstract void storedSizeChange(int storeSizeIncrease); + + + @Override + public void onDelete() + { + try + { + Connection conn = newAutoCommitConnection(); + try + { + for (String tableName : ALL_TABLES) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute("DROP TABLE " + tableName); + } + finally + { + stmt.close(); + } + } + } + finally + { + conn.close(); + } + } + catch(SQLException e) + { + getLogger().error("Exception while deleting store tables", e); + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractMemoryMessageStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractMemoryMessageStore.java new file mode 100644 index 0000000000..3abf083026 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/AbstractMemoryMessageStore.java @@ -0,0 +1,147 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.model.VirtualHost; + +/** A simple message store that stores the messages in a thread-safe structure in memory. */ +abstract public class AbstractMemoryMessageStore extends NullMessageStore +{ + private final AtomicLong _messageId = new AtomicLong(1); + private final AtomicBoolean _closed = new AtomicBoolean(false); + + private static final Transaction IN_MEMORY_TRANSACTION = new Transaction() + { + @Override + public StoreFuture commitTranAsync() throws AMQStoreException + { + return StoreFuture.IMMEDIATE_FUTURE; + } + + @Override + public void enqueueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException + { + } + + @Override + public void dequeueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException + { + } + + @Override + public void commitTran() throws AMQStoreException + { + } + + @Override + public void abortTran() throws AMQStoreException + { + } + + @Override + public void removeXid(long format, byte[] globalId, byte[] branchId) + { + } + + @Override + public void recordXid(long format, byte[] globalId, byte[] branchId, Record[] enqueues, Record[] dequeues) + { + } + }; + + private final StateManager _stateManager; + private final EventManager _eventManager = new EventManager(); + + public AbstractMemoryMessageStore() + { + _stateManager = new StateManager(_eventManager); + } + + @Override + public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) throws Exception + { + _stateManager.attainState(State.INITIALISING); + } + + @Override + public void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler recoveryHandler, + TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception + { + if(_stateManager.isInState(State.INITIAL)) + { + _stateManager.attainState(State.INITIALISING); + } + _stateManager.attainState(State.INITIALISED); + } + + @Override + public void activate() throws Exception + { + + if(_stateManager.isInState(State.INITIALISING)) + { + _stateManager.attainState(State.INITIALISED); + } + _stateManager.attainState(State.ACTIVATING); + + _stateManager.attainState(State.ACTIVE); + } + + @Override + public StoredMessage addMessage(StorableMessageMetaData metaData) + { + final long id = _messageId.getAndIncrement(); + StoredMemoryMessage message = new StoredMemoryMessage(id, metaData); + + return message; + } + + @Override + public Transaction newTransaction() + { + return IN_MEMORY_TRANSACTION; + } + + @Override + public boolean isPersistent() + { + return false; + } + + @Override + public void close() throws Exception + { + _stateManager.attainState(State.CLOSING); + _closed.getAndSet(true); + _stateManager.attainState(State.CLOSED); + } + + @Override + public void addEventListener(EventListener eventListener, Event... events) + { + _eventManager.addEventListener(eventListener, events); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.java new file mode 100755 index 0000000000..a3534d3fa5 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.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.store; + +import org.apache.qpid.framing.FieldTable; + +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.UUID; + +public interface ConfigurationRecoveryHandler +{ + void beginConfigurationRecovery(DurableConfigurationStore store, int configVersion); + + void configuredObject(UUID id, String type, Map attributes); + + /** + * + * @return the model version of the configuration + */ + int completeConfigurationRecovery(); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfiguredObjectRecord.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfiguredObjectRecord.java new file mode 100644 index 0000000000..44490385d9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/ConfiguredObjectRecord.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.store; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; + +public class ConfiguredObjectRecord +{ + private UUID _id; + private String _type; + private Map _attributes; + + public ConfiguredObjectRecord(UUID id, String type, Map attributes) + { + super(); + _id = id; + _type = type; + _attributes = Collections.unmodifiableMap(new LinkedHashMap(attributes)); + } + + public UUID getId() + { + return _id; + } + + public String getType() + { + return _type; + } + + public Map getAttributes() + { + return _attributes; + } + + @Override + public String toString() + { + return "ConfiguredObjectRecord [id=" + _id + ", type=" + _type + ", attributes=" + _attributes + "]"; + } + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + + ConfiguredObjectRecord that = (ConfiguredObjectRecord) o; + + return _type.equals(that._type) && _id.equals(that._id) && _attributes.equals(that._attributes); + } + + @Override + public int hashCode() + { + int result = _id.hashCode(); + result = 31 * result + _type.hashCode(); + result = 31 * result + _attributes.hashCode(); + return result; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DependencyListener.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DependencyListener.java new file mode 100644 index 0000000000..120c904cf7 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DependencyListener.java @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +import java.util.UUID; + +interface DependencyListener +{ + void dependencyResolved(String type, UUID id, Object o); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationRecoverer.java new file mode 100644 index 0000000000..7fb80bde96 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationRecoverer.java @@ -0,0 +1,242 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ConfigStoreMessages; +import org.apache.qpid.server.logging.subjects.MessageStoreLogSubject; + +import static org.apache.qpid.server.model.VirtualHost.CURRENT_CONFIG_VERSION; + +public class DurableConfigurationRecoverer implements ConfigurationRecoveryHandler +{ + private static final Logger _logger = Logger.getLogger(DurableConfigurationRecoverer.class); + + private final Map> _resolvedObjects = new HashMap>(); + + private final Map> _unresolvedObjects = + new HashMap>(); + + private final Map>> _dependencyListeners = + new HashMap>>(); + private final Map _recoverers; + private final UpgraderProvider _upgraderProvider; + + private DurableConfigurationStoreUpgrader _upgrader; + + private DurableConfigurationStore _store; + private final String _name; + + private MessageStoreLogSubject _logSubject; + + public DurableConfigurationRecoverer(final String name, + Map recoverers, + UpgraderProvider upgraderProvider) + { + _recoverers = recoverers; + _name = name; + _upgraderProvider = upgraderProvider; + } + + @Override + public void beginConfigurationRecovery(final DurableConfigurationStore store, final int configVersion) + { + _logSubject = new MessageStoreLogSubject(_name, store.getClass().getSimpleName()); + + _store = store; + _upgrader = _upgraderProvider.getUpgrader(configVersion, this); + } + + @Override + public void configuredObject(final UUID id, final String type, final Map attributes) + { + _upgrader.configuredObject(id, type, attributes); + } + + void onConfiguredObject(final UUID id, final String type, final Map attributes) + { + DurableConfiguredObjectRecoverer recoverer = getRecoverer(type); + if(recoverer == null) + { + throw new IllegalConfigurationException("Unkown type for configured object: " + type); + } + recoverer.load(this, id, attributes); + } + + private DurableConfiguredObjectRecoverer getRecoverer(final String type) + { + DurableConfiguredObjectRecoverer recoverer = _recoverers.get(type); + return recoverer; + } + + @Override + public int completeConfigurationRecovery() + { + _upgrader.complete(); + checkUnresolvedDependencies(); + applyUpgrade(); + + CurrentActor.get().message(_logSubject, ConfigStoreMessages.RECOVERY_COMPLETE()); + return CURRENT_CONFIG_VERSION; + } + + private void applyUpgrade() + { + + final Collection updates = new ArrayList(); + final Collection deletes = new ArrayList(); + for(Map.Entry entry : _upgrader.getUpdatedRecords().entrySet()) + { + if(entry.getValue() != null) + { + updates.add(entry.getValue()); + } + else + { + deletes.add(entry.getKey()); + } + } + + try + { + if(!updates.isEmpty()) + { + _store.update(true,updates.toArray(new ConfiguredObjectRecord[updates.size()])); + } + if(!deletes.isEmpty()) + { + _store.removeConfiguredObjects(deletes.toArray(new UUID[deletes.size()])); + } + } + catch (AMQStoreException e) + { + // TODO better exception + throw new RuntimeException("Unable to update config store when upgrading"); + } + + } + + private void checkUnresolvedDependencies() + { + if(_unresolvedObjects != null && !_unresolvedObjects.isEmpty()) + { + boolean unresolvedObjectsExist = false; + for(Map.Entry>entry : _unresolvedObjects.entrySet()) + { + for(Map.Entry obj : entry.getValue().entrySet()) + { + unresolvedObjectsExist = true; + StringBuilder errorMessage = new StringBuilder("Durable configured object of type "); + errorMessage.append(entry.getKey()).append(" with id ").append(obj.getKey()) + .append(" has unresolved dependencies: "); + for(UnresolvedDependency dep : obj.getValue().getUnresolvedDependencies()) + { + errorMessage.append(dep.getType()).append(" with id ").append(dep.getId()).append("; "); + } + _logger.error(errorMessage); + } + } + if(unresolvedObjectsExist) + { + throw new IllegalConfigurationException("Durable configuration has unresolved dependencies"); + } + } + } + + void addResolutionListener(final String type, + final UUID id, + final DependencyListener dependencyListener) + { + Map> typeListeners = _dependencyListeners.get(type); + if(typeListeners == null) + { + typeListeners = new HashMap>(); + _dependencyListeners.put(type, typeListeners); + } + List objectListeners = typeListeners.get(id); + if(objectListeners == null) + { + objectListeners = new ArrayList(); + typeListeners.put(id, objectListeners); + } + objectListeners.add(dependencyListener); + + } + + Object getResolvedObject(final String type, final UUID id) + { + Map objects = _resolvedObjects.get(type); + return objects == null ? null : objects.get(id); + } + + void resolve(final String type, final UUID id, final Object object) + { + Map typeObjects = _resolvedObjects.get(type); + if(typeObjects == null) + { + typeObjects = new HashMap(); + _resolvedObjects.put(type, typeObjects); + } + typeObjects.put(id, object); + Map unresolved = _unresolvedObjects.get(type); + if(unresolved != null) + { + unresolved.remove(id); + } + + Map> typeListeners = _dependencyListeners.get(type); + if(typeListeners != null) + { + List listeners = typeListeners.remove(id); + if(listeners != null) + { + for(DependencyListener listener : listeners) + { + listener.dependencyResolved(type, id, object); + } + } + } + } + + void addUnresolvedObject(final String type, + final UUID id, + final UnresolvedObject obj) + { + Map typeObjects = _unresolvedObjects.get(type); + if(typeObjects == null) + { + typeObjects = new HashMap(); + _unresolvedObjects.put(type, typeObjects); + } + typeObjects.put(id, obj); + } + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.java new file mode 100755 index 0000000000..6b0748b0c3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.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.store; + +import java.util.Map; +import java.util.UUID; +import org.apache.commons.configuration.Configuration; + +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.queue.AMQQueue; + +public interface DurableConfigurationStore +{ + + public static interface Source + { + DurableConfigurationStore getDurableConfigurationStore(); + } + + /** + * Called after instantiation in order to configure the message store. A particular implementation can define + * whatever parameters it wants. + * + * + * + * + * + * @param virtualHost + * @param recoveryHandler Handler to be called as the store recovers on start up + * @throws Exception If any error occurs that means the store is unable to configure itself. + */ + void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) throws Exception; + + + /** + * Makes the specified object persistent. + * + * @param id The id of the object to persist. + * @param type The type of the object to persist + * @param attributes the attributes of the object to persist + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void create(UUID id, String type, Map attributes) throws AMQStoreException; + + /** + * Removes the specified persistent configured object. + * + * @param id The id of the object to remove. + * @param type The type of the object to remove + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void remove(UUID id, String type) throws AMQStoreException; + + public UUID[] removeConfiguredObjects(UUID... objects) throws AMQStoreException; + + + /** + * Updates the specified object in the persistent store, IF it is already present. If the object + * is not present in the store, it will not be added. + * + * @param id The id of the object to update. + * @param type The type of the object to update + * @param attributes the updated attributes + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void update(UUID id, String type, Map attributes) throws AMQStoreException; + + + public void update(ConfiguredObjectRecord... records) throws AMQStoreException; + public void update(boolean createIfNecessary, ConfiguredObjectRecord... records) throws AMQStoreException; + + + void close() throws Exception; +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreCreator.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreCreator.java new file mode 100644 index 0000000000..3a69f802f0 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreCreator.java @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.plugin.DurableConfigurationStoreFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; + +public class DurableConfigurationStoreCreator +{ + private Map _factories = new HashMap(); + + public DurableConfigurationStoreCreator() + { + QpidServiceLoader qpidServiceLoader = new QpidServiceLoader(); + Iterable factories = qpidServiceLoader.atLeastOneInstanceOf(DurableConfigurationStoreFactory.class); + for (DurableConfigurationStoreFactory durableConfigurationStoreFactory : factories) + { + String type = durableConfigurationStoreFactory.getType(); + DurableConfigurationStoreFactory factory = _factories.put(type.toLowerCase(), durableConfigurationStoreFactory); + if (factory != null) + { + throw new IllegalStateException("DurableConfigurationStoreFactory with type name '" + type + + "' is already registered using class '" + factory.getClass().getName() + "', can not register class '" + + durableConfigurationStoreFactory.getClass().getName() + "'"); + } + } + } + + public boolean isValidType(String storeType) + { + return _factories.containsKey(storeType.toLowerCase()); + } + + + public DurableConfigurationStore createMessageStore(String storeType) + { + DurableConfigurationStoreFactory factory = _factories.get(storeType.toLowerCase()); + if (factory == null) + { + throw new IllegalConfigurationException("Unknown store type: " + storeType + + ". Supported types: " + _factories.keySet()); + } + return factory.createDurableConfigurationStore(); + } + + public Collection getFactories() + { + return Collections.unmodifiableCollection(_factories.values()); + } + + public Collection getStoreTypes() + { + return Collections.unmodifiableCollection(_factories.keySet()); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreHelper.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreHelper.java new file mode 100644 index 0000000000..d311685375 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreHelper.java @@ -0,0 +1,145 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; + +import java.util.Set; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueArgumentsConverter; + +public class DurableConfigurationStoreHelper +{ + + private static final String BINDING = Binding.class.getSimpleName(); + private static final String EXCHANGE = Exchange.class.getSimpleName(); + private static final String QUEUE = Queue.class.getSimpleName(); + private static final Set QUEUE_ARGUMENTS_EXCLUDES = new HashSet(Arrays.asList(Queue.NAME, + Queue.OWNER, + Queue.EXCLUSIVE, + Queue.ALTERNATE_EXCHANGE)); + + public static void updateQueue(DurableConfigurationStore store, AMQQueue queue) throws AMQStoreException + { + Map attributesMap = new LinkedHashMap(); + attributesMap.put(Queue.NAME, queue.getName()); + attributesMap.put(Queue.OWNER, queue.getOwner()); + attributesMap.put(Queue.EXCLUSIVE, queue.isExclusive()); + + if (queue.getAlternateExchange() != null) + { + attributesMap.put(Queue.ALTERNATE_EXCHANGE, queue.getAlternateExchange().getId()); + } + + Collection availableAttrs = queue.getAvailableAttributes(); + + for(String attrName : availableAttrs) + { + if(!QUEUE_ARGUMENTS_EXCLUDES.contains(attrName)) + { + attributesMap.put(attrName, queue.getAttribute(attrName)); + } + } + + store.update(queue.getId(), QUEUE, attributesMap); + } + + public static void createQueue(DurableConfigurationStore store, AMQQueue queue) + throws AMQStoreException + { + Map attributesMap = new HashMap(); + attributesMap.put(Queue.NAME, queue.getName()); + attributesMap.put(Queue.OWNER, queue.getOwner()); + attributesMap.put(Queue.EXCLUSIVE, queue.isExclusive()); + if (queue.getAlternateExchange() != null) + { + attributesMap.put(Queue.ALTERNATE_EXCHANGE, queue.getAlternateExchange().getId()); + } + + for(String attrName : queue.getAvailableAttributes()) + { + if(!QUEUE_ARGUMENTS_EXCLUDES.contains(attrName)) + { + attributesMap.put(attrName, queue.getAttribute(attrName)); + } + } + store.create(queue.getId(), QUEUE,attributesMap); + } + + public static void removeQueue(DurableConfigurationStore store, AMQQueue queue) throws AMQStoreException + { + store.remove(queue.getId(), QUEUE); + } + + public static void createExchange(DurableConfigurationStore store, org.apache.qpid.server.exchange.Exchange exchange) + throws AMQStoreException + { + Map attributesMap = new HashMap(); + attributesMap.put(Exchange.NAME, exchange.getName()); + attributesMap.put(Exchange.TYPE, exchange.getTypeName()); + attributesMap.put(Exchange.LIFETIME_POLICY, exchange.isAutoDelete() ? LifetimePolicy.AUTO_DELETE.name() + : LifetimePolicy.PERMANENT.name()); + store.create(exchange.getId(), EXCHANGE, attributesMap); + + } + + + public static void removeExchange(DurableConfigurationStore store, org.apache.qpid.server.exchange.Exchange exchange) + throws AMQStoreException + { + store.remove(exchange.getId(), EXCHANGE); + } + + public static void createBinding(DurableConfigurationStore store, org.apache.qpid.server.binding.Binding binding) + throws AMQStoreException + { + Map attributesMap = new HashMap(); + attributesMap.put(Binding.NAME, binding.getBindingKey()); + attributesMap.put(Binding.EXCHANGE, binding.getExchange().getId()); + attributesMap.put(Binding.QUEUE, binding.getQueue().getId()); + Map arguments = binding.getArguments(); + if (arguments != null) + { + attributesMap.put(Binding.ARGUMENTS, arguments); + } + store.create(binding.getId(), BINDING, attributesMap); + } + + + public static void removeBinding(DurableConfigurationStore store, org.apache.qpid.server.binding.Binding binding) + throws AMQStoreException + { + store.remove(binding.getId(), BINDING); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreUpgrader.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreUpgrader.java new file mode 100644 index 0000000000..1d3e4cc672 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfigurationStoreUpgrader.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.store; + +import java.util.Map; +import java.util.UUID; + +public interface DurableConfigurationStoreUpgrader +{ + void configuredObject(UUID id, String type, Map attributes); + + void complete(); + + void setNextUpgrader(DurableConfigurationStoreUpgrader upgrader); + + Map getUpdatedRecords(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfiguredObjectRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfiguredObjectRecoverer.java new file mode 100644 index 0000000000..e065728bd3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/DurableConfiguredObjectRecoverer.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.store; + +import java.util.Map; +import java.util.UUID; + +public interface DurableConfiguredObjectRecoverer +{ + public void load(final DurableConfigurationRecoverer durableConfigurationRecoverer, + final UUID id, + final Map attributes); + + public String getType(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/Event.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/Event.java new file mode 100644 index 0000000000..c681126c11 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/Event.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.store; + +public enum Event +{ + BEFORE_INIT, + AFTER_INIT, + + BEFORE_ACTIVATE, + AFTER_ACTIVATE, + + BEFORE_PASSIVATE, + AFTER_PASSIVATE, + + BEFORE_CLOSE, + AFTER_CLOSE, + + BEFORE_QUIESCE, + AFTER_QUIESCE, + BEFORE_RESTART, + + PERSISTENT_MESSAGE_SIZE_OVERFULL, + PERSISTENT_MESSAGE_SIZE_UNDERFULL +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/EventListener.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/EventListener.java new file mode 100644 index 0000000000..33ae7b5b24 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/EventListener.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.store; + +public interface EventListener +{ + public void event(Event event); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/EventManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/EventManager.java new file mode 100644 index 0000000000..bf3de2611d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/EventManager.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.store; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; + +public class EventManager +{ + private Map> _listeners = new EnumMap> (Event.class); + private static final Logger _LOGGER = Logger.getLogger(EventManager.class); + + public synchronized void addEventListener(EventListener listener, Event... events) + { + for(Event event : events) + { + List list = _listeners.get(event); + if(list == null) + { + list = new ArrayList(); + _listeners.put(event,list); + } + list.add(listener); + } + } + + public synchronized void notifyEvent(Event event) + { + if (_listeners.containsKey(event)) + { + if(_LOGGER.isDebugEnabled()) + { + _LOGGER.debug("Received event " + event); + } + + for (EventListener listener : _listeners.get(event)) + { + listener.event(event); + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/HAMessageStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/HAMessageStore.java new file mode 100644 index 0000000000..59483751ca --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/HAMessageStore.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.store; + +public interface HAMessageStore extends MessageStore +{ + /** + * Used to indicate that a store requires to make itself unavailable for read and read/write + * operations. + */ + void passivate(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStore.java new file mode 100644 index 0000000000..8eed1fa5a4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStore.java @@ -0,0 +1,513 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Model; +import org.apache.qpid.server.model.VirtualHost; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; + +public class JsonFileConfigStore implements DurableConfigurationStore +{ + private static final Model MODEL = Model.getInstance(); + + private static final Map> CLASS_NAME_MAPPING = generateClassNameMap(VirtualHost.class); + public static final String TYPE = "JSON"; + + private final Map _objectsById = new HashMap(); + private final Map> _idsByType = new HashMap>(); + private final ObjectMapper _objectMapper = new ObjectMapper(); + + private String _directoryName; + private String _name; + private FileLock _fileLock; + private String _configFileName; + private String _backupFileName; + private int _configVersion; + + public JsonFileConfigStore() + { + _objectMapper.enable(SerializationConfig.Feature.INDENT_OUTPUT); + } + + @Override + public void configureConfigStore(final VirtualHost virtualHost, final ConfigurationRecoveryHandler recoveryHandler) + throws Exception + { + _name = virtualHost.getName(); + + Object storePathAttr = virtualHost.getAttribute(VirtualHost.CONFIG_STORE_PATH); + if(!(storePathAttr instanceof String)) + { + throw new AMQStoreException("Cannot determine path for configuration storage"); + } + _directoryName = (String) storePathAttr; + _configFileName = _name + ".json"; + _backupFileName = _name + ".bak"; + checkDirectoryIsWritable(_directoryName); + getFileLock(); + + if(!fileExists(_configFileName)) + { + if(!fileExists(_backupFileName)) + { + File newFile = new File(_directoryName, _configFileName); + _objectMapper.writeValue(newFile, Collections.emptyMap()); + } + else + { + renameFile(_backupFileName, _configFileName); + } + } + + + load(); + recoveryHandler.beginConfigurationRecovery(this,_configVersion); + List records = new ArrayList(_objectsById.values()); + for(ConfiguredObjectRecord record : records) + { + recoveryHandler.configuredObject(record.getId(), record.getType(), record.getAttributes()); + } + int oldConfigVersion = _configVersion; + _configVersion = recoveryHandler.completeConfigurationRecovery(); + if(oldConfigVersion != _configVersion) + { + save(); + } + } + + private void renameFile(String fromFileName, String toFileName) throws AMQStoreException + { + File toFile = new File(_directoryName, toFileName); + if(toFile.exists()) + { + if(!toFile.delete()) + { + throw new AMQStoreException("Cannot delete file " + toFile.getAbsolutePath()); + } + } + File fromFile = new File(_directoryName, fromFileName); + + if(!fromFile.renameTo(toFile)) + { + throw new AMQStoreException("Cannot rename file " + fromFile.getAbsolutePath() + " to " + toFile.getAbsolutePath()); + } + } + + private boolean fileExists(String fileName) + { + File file = new File(_directoryName, fileName); + return file.exists(); + } + + private void getFileLock() throws IOException, AMQStoreException + { + File lockFile = new File(_directoryName, _name + ".lck"); + lockFile.createNewFile(); + + FileOutputStream out = new FileOutputStream(lockFile); + FileChannel channel = out.getChannel(); + try + { + _fileLock = channel.tryLock(); + } + catch(OverlappingFileLockException e) + { + _fileLock = null; + } + if(_fileLock == null) + { + throw new AMQStoreException("Cannot get lock on file " + lockFile.getAbsolutePath() + " is another instance running?"); + } + lockFile.deleteOnExit(); + } + + private void checkDirectoryIsWritable(String directoryName) throws AMQStoreException + { + File dir = new File(directoryName); + if(dir.exists()) + { + if(dir.isDirectory()) + { + if(!dir.canWrite()) + { + throw new AMQStoreException("Configuration path " + directoryName + " exists, but is not writable"); + } + + } + else + { + throw new AMQStoreException("Configuration path " + directoryName + " exists, but is not a directory"); + } + } + else if(!dir.mkdirs()) + { + throw new AMQStoreException("Cannot create directory " + directoryName); + } + } + + private void load() throws IOException + { + Map data = _objectMapper.readValue(new File(_directoryName,_configFileName),Map.class); + Collection> childClasses = + MODEL.getChildTypes(VirtualHost.class); + String modelVersion = (String) data.remove("modelVersion"); + Object configVersion; + if((configVersion = data.remove("configVersion")) instanceof Integer) + { + _configVersion = (Integer) configVersion; + } + for(Class childClass : childClasses) + { + final String type = childClass.getSimpleName(); + String attrName = type.toLowerCase() + "s"; + Object children = data.remove(attrName); + if(children != null) + { + if(children instanceof Collection) + { + for(Object child : (Collection)children) + { + if(child instanceof Map) + { + loadChild(childClass, (Map)child, VirtualHost.class, null); + } + } + } + } + } + } + + private void loadChild(final Class clazz, + final Map data, + final Class parentClass, + final UUID parentId) + { + Collection> childClasses = + MODEL.getChildTypes(clazz); + String idStr = (String) data.remove("id"); + final UUID id = UUID.fromString(idStr); + final String type = clazz.getSimpleName(); + + for(Class childClass : childClasses) + { + final String childType = childClass.getSimpleName(); + String attrName = childType.toLowerCase() + "s"; + Object children = data.remove(attrName); + if(children != null) + { + if(children instanceof Collection) + { + for(Object child : (Collection)children) + { + if(child instanceof Map) + { + loadChild(childClass, (Map)child, clazz, id); + } + } + } + } + + } + if(parentId != null) + { + data.put(parentClass.getSimpleName().toLowerCase(),parentId); + for(Class otherParent : MODEL.getParentTypes(clazz)) + { + if(otherParent != parentClass) + { + final String otherParentAttr = otherParent.getSimpleName().toLowerCase(); + Object otherParentId = data.get(otherParentAttr); + if(otherParentId instanceof String) + { + try + { + data.put(otherParentAttr, UUID.fromString((String) otherParentId)); + } + catch(IllegalArgumentException e) + { + // + } + } + } + + } + } + + _objectsById.put(id, new ConfiguredObjectRecord(id, type, data)); + List idsForType = _idsByType.get(type); + if(idsForType == null) + { + idsForType = new ArrayList(); + _idsByType.put(type, idsForType); + } + idsForType.add(id); + + } + + @Override + public synchronized void create(final UUID id, final String type, final Map attributes) throws AMQStoreException + { + if(_objectsById.containsKey(id)) + { + throw new AMQStoreException("Object with id " + id + " already exists"); + } + else if(!CLASS_NAME_MAPPING.containsKey(type)) + { + throw new AMQStoreException("Cannot create object of unknown type " + type); + } + else + { + ConfiguredObjectRecord record = new ConfiguredObjectRecord(id, type, attributes); + _objectsById.put(id, record); + List idsForType = _idsByType.get(type); + if(idsForType == null) + { + idsForType = new ArrayList(); + _idsByType.put(type, idsForType); + } + idsForType.add(id); + save(); + } + } + + private void save() throws AMQStoreException + { + Collection> childClasses = + MODEL.getChildTypes(VirtualHost.class); + + Map virtualHostMap = new LinkedHashMap(); + virtualHostMap.put("modelVersion", Model.MODEL_VERSION); + virtualHostMap.put("configVersion", _configVersion); + + for(Class childClass : childClasses) + { + final String type = childClass.getSimpleName(); + String attrName = type.toLowerCase() + "s"; + List childIds = _idsByType.get(type); + if(childIds != null && !childIds.isEmpty()) + { + List> entities = new ArrayList>(); + for(UUID id : childIds) + { + entities.add(build(childClass,id)); + } + virtualHostMap.put(attrName, entities); + } + } + + try + { + + File tmpFile = File.createTempFile("cfg","tmp", new File(_directoryName)); + tmpFile.deleteOnExit(); + _objectMapper.writeValue(tmpFile,virtualHostMap); + renameFile(_configFileName,_backupFileName); + renameFile(tmpFile.getName(),_configFileName); + tmpFile.delete(); + File backupFile = new File(_directoryName, _backupFileName); + backupFile.delete(); + + } + catch (IOException e) + { + throw new AMQStoreException("Cannot save to store", e); + } + } + + private Map build(final Class type, final UUID id) + { + ConfiguredObjectRecord record = _objectsById.get(id); + Map map = new LinkedHashMap(); + map.put("id", id); + map.putAll(record.getAttributes()); + map.remove(MODEL.getParentTypes(type).iterator().next().getSimpleName().toLowerCase()); + + Collection> childClasses = + new ArrayList>(MODEL.getChildTypes(type)); + + for(Class childClass : childClasses) + { + // only add if this is the "first" parent + if(MODEL.getParentTypes(childClass).iterator().next() == type) + { + String attrName = childClass.getSimpleName().toLowerCase() + "s"; + List childIds = _idsByType.get(childClass.getSimpleName()); + if(childIds != null) + { + List> entities = new ArrayList>(); + for(UUID childId : childIds) + { + ConfiguredObjectRecord childRecord = _objectsById.get(childId); + final String parentArg = type.getSimpleName().toLowerCase(); + if(id.toString().equals(String.valueOf(childRecord.getAttributes().get(parentArg)))) + { + entities.add(build(childClass,childId)); + } + } + if(!entities.isEmpty()) + { + map.put(attrName,entities); + } + } + } + } + + return map; + } + + @Override + public void remove(final UUID id, final String type) throws AMQStoreException + { + removeConfiguredObjects(id); + } + + @Override + public synchronized UUID[] removeConfiguredObjects(final UUID... objects) throws AMQStoreException + { + List removedIds = new ArrayList(); + for(UUID id : objects) + { + ConfiguredObjectRecord record = _objectsById.remove(id); + if(record != null) + { + removedIds.add(id); + _idsByType.get(record.getType()).remove(id); + } + } + save(); + return removedIds.toArray(new UUID[removedIds.size()]); + } + + @Override + public void update(final UUID id, final String type, final Map attributes) throws AMQStoreException + { + update(false, new ConfiguredObjectRecord(id, type, attributes)); + } + + @Override + public void update(final ConfiguredObjectRecord... records) throws AMQStoreException + { + update(false, records); + } + + @Override + public void update(final boolean createIfNecessary, final ConfiguredObjectRecord... records) + throws AMQStoreException + { + for(ConfiguredObjectRecord record : records) + { + final UUID id = record.getId(); + final String type = record.getType(); + + if(_objectsById.containsKey(id)) + { + final ConfiguredObjectRecord existingRecord = _objectsById.get(id); + if(!type.equals(existingRecord.getType())) + { + throw new AMQStoreException("Cannot change the type of record " + id + " from type " + + existingRecord.getType() + " to type " + type); + } + } + else if(!createIfNecessary) + { + throw new AMQStoreException("Cannot update record with id " + id + + " of type " + type + " as it does not exist"); + } + else if(!CLASS_NAME_MAPPING.containsKey(type)) + { + throw new AMQStoreException("Cannot update record of unknown type " + type); + } + } + for(ConfiguredObjectRecord record : records) + { + final UUID id = record.getId(); + final String type = record.getType(); + if(_objectsById.put(id, record) == null) + { + List idsForType = _idsByType.get(type); + if(idsForType == null) + { + idsForType = new ArrayList(); + _idsByType.put(type, idsForType); + } + idsForType.add(id); + } + } + + save(); + } + + public void close() throws Exception + { + try + { + releaseFileLock(); + } + finally + { + _fileLock = null; + _idsByType.clear(); + _objectsById.clear(); + } + + } + + private void releaseFileLock() throws IOException + { + _fileLock.release(); + _fileLock.channel().close(); + } + + + private static Map> generateClassNameMap(final Class clazz) + { + Map>map = new HashMap>(); + map.put(clazz.getSimpleName().toString(), clazz); + Collection> childClasses = MODEL.getChildTypes(clazz); + if(childClasses != null) + { + for(Class childClass : childClasses) + { + map.putAll(generateClassNameMap(childClass)); + } + } + return map; + } + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStoreFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStoreFactory.java new file mode 100644 index 0000000000..374a35d10d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/JsonFileConfigStoreFactory.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.store; + +import java.util.Map; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.plugin.DurableConfigurationStoreFactory; + +public class JsonFileConfigStoreFactory implements DurableConfigurationStoreFactory +{ + @Override + public String getType() + { + return JsonFileConfigStore.TYPE; + } + + @Override + public DurableConfigurationStore createDurableConfigurationStore() + { + return new JsonFileConfigStore(); + } + + @Override + public void validateAttributes(Map attributes) + { + Object storePath = attributes.get(VirtualHost.CONFIG_STORE_PATH); + if(!(storePath instanceof String)) + { + throw new IllegalArgumentException("Attribute '"+ VirtualHost.CONFIG_STORE_PATH + +"' is required and must be of type String."); + + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageMetaDataTypeRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageMetaDataTypeRegistry.java new file mode 100644 index 0000000000..64f3ab15ee --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageMetaDataTypeRegistry.java @@ -0,0 +1,65 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +import org.apache.qpid.server.plugin.MessageMetaDataType; +import org.apache.qpid.server.plugin.QpidServiceLoader; + +public class MessageMetaDataTypeRegistry +{ + private static MessageMetaDataType[] values; + + static + { + int maxOrdinal = -1; + + Iterable messageMetaDataTypes = + new QpidServiceLoader().atLeastOneInstanceOf(MessageMetaDataType.class); + + for(MessageMetaDataType type : messageMetaDataTypes) + { + if(type.ordinal()>maxOrdinal) + { + maxOrdinal = type.ordinal(); + } + } + values = new MessageMetaDataType[maxOrdinal+1]; + for(MessageMetaDataType type : new QpidServiceLoader().instancesOf(MessageMetaDataType.class)) + { + if(values[type.ordinal()] != null) + { + throw new IllegalStateException("Multiple MessageDataType (" + +values[type.ordinal()].getClass().getName() + +", " + + type.getClass().getName() + + ") defined for the same ordinal value: " + type.ordinal()); + } + values[type.ordinal()] = type; + } + } + + + public static MessageMetaDataType fromOrdinal(int ordinal) + { + return values[ordinal]; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStore.java new file mode 100644 index 0000000000..996d71d51d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStore.java @@ -0,0 +1,74 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +import org.apache.qpid.server.model.VirtualHost; + +/** + * MessageStore defines the interface to a storage area, which can be used to preserve the state of messages. + * + */ +public interface MessageStore +{ + /** + * Called after instantiation in order to configure the message store. A particular implementation can define + * whatever parameters it wants. + * + * + * + * + * @param virtualHost + * @param messageRecoveryHandler Handler to be called as the store recovers on start up + * @param tlogRecoveryHandler + * @throws Exception If any error occurs that means the store is unable to configure itself. + */ + void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler messageRecoveryHandler, + TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception; + + void activate() throws Exception; + + public StoredMessage addMessage(T metaData); + + + /** + * Is this store capable of persisting the data + * + * @return true if this store is capable of persisting data + */ + boolean isPersistent(); + + Transaction newTransaction(); + + /** + * Called to close and cleanup any resources used by the message store. + * + * @throws Exception If the close fails. + */ + void close() throws Exception; + + void addEventListener(EventListener eventListener, Event... events); + + String getStoreLocation(); + + String getStoreType(); + + void onDelete(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java new file mode 100644 index 0000000000..b0a736c66c --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.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.store; + +import org.apache.qpid.AMQException; + +/** + * 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-core/src/main/java/org/apache/qpid/server/store/MessageStoreConstants.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreConstants.java new file mode 100644 index 0000000000..93b669e6e4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreConstants.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +public class MessageStoreConstants +{ + + public static final String ENVIRONMENT_PATH_PROPERTY = "environment-path"; + public static final String OVERFULL_SIZE_PROPERTY = "overfull-size"; + public static final String UNDERFULL_SIZE_PROPERTY = "underfull-size"; + public static final String OVERFULL_SIZE_ATTRIBUTE = "storeOverfullSize"; + public static final String UNDERFULL_SIZE_ATTRIBUTE = "storeUnderfullSize"; + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreCreator.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreCreator.java new file mode 100644 index 0000000000..a8013b8e9b --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreCreator.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.store; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.plugin.MessageStoreFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; + +public class MessageStoreCreator +{ + private Map _factories = new HashMap(); + + public MessageStoreCreator() + { + QpidServiceLoader qpidServiceLoader = new QpidServiceLoader(); + Iterable factories = qpidServiceLoader.atLeastOneInstanceOf(MessageStoreFactory.class); + for (MessageStoreFactory messageStoreFactory : factories) + { + String type = messageStoreFactory.getType(); + MessageStoreFactory factory = _factories.put(type.toLowerCase(), messageStoreFactory); + if (factory != null) + { + throw new IllegalStateException("MessageStoreFactory with type name '" + type + + "' is already registered using class '" + factory.getClass().getName() + "', can not register class '" + + messageStoreFactory.getClass().getName() + "'"); + } + } + } + + public boolean isValidType(String storeType) + { + return _factories.containsKey(storeType.toLowerCase()); + } + + + public MessageStore createMessageStore(String storeType) + { + MessageStoreFactory factory = _factories.get(storeType.toLowerCase()); + if (factory == null) + { + throw new IllegalConfigurationException("Unknown store type: " + storeType + + ". Supported types: " + _factories.keySet()); + } + return factory.createMessageStore(); + } + + public Collection getFactories() + { + return Collections.unmodifiableCollection(_factories.values()); + } + + public Collection getStoreTypes() + { + return Collections.unmodifiableCollection(_factories.keySet()); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreRecoveryHandler.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreRecoveryHandler.java new file mode 100755 index 0000000000..ba65b8e1ec --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/MessageStoreRecoveryHandler.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.store; + +public interface MessageStoreRecoveryHandler +{ + StoredMessageRecoveryHandler begin(); + + public static interface StoredMessageRecoveryHandler + { + void message(StoredMessage message); + + void completeMessageRecovery(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NonNullUpgrader.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NonNullUpgrader.java new file mode 100644 index 0000000000..a671e93b26 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NonNullUpgrader.java @@ -0,0 +1,62 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public abstract class NonNullUpgrader implements DurableConfigurationStoreUpgrader +{ + private DurableConfigurationStoreUpgrader _nextUpgrader; + private final Map _updates = new HashMap(); + + public final void setNextUpgrader(final DurableConfigurationStoreUpgrader upgrader) + { + if(_nextUpgrader == null) + { + _nextUpgrader = upgrader; + } + else + { + _nextUpgrader.setNextUpgrader(upgrader); + } + } + + protected DurableConfigurationStoreUpgrader getNextUpgrader() + { + return _nextUpgrader; + } + + protected Map getUpdateMap() + { + return _updates; + } + + @Override + public final Map getUpdatedRecords() + { + final Map updates = new HashMap(_updates); + updates.putAll(_nextUpgrader.getUpdatedRecords()); + return updates; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullMessageStore.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullMessageStore.java new file mode 100644 index 0000000000..57dbfabaa4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullMessageStore.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 java.util.Map; +import java.util.UUID; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.model.VirtualHost; + +public abstract class NullMessageStore implements MessageStore, DurableConfigurationStore +{ + @Override + public void configureConfigStore(VirtualHost virtualHost, ConfigurationRecoveryHandler recoveryHandler) throws Exception + { + } + + @Override + public void update(UUID id, String type, Map attributes) + { + } + + @Override + public void update(ConfiguredObjectRecord... records) throws AMQStoreException + { + } + + @Override + public void update(boolean createIfNecessary, ConfiguredObjectRecord... records) throws AMQStoreException + { + } + + + @Override + public void remove(UUID id, String type) + { + } + + @Override + public UUID[] removeConfiguredObjects(final UUID... objects) throws AMQStoreException + { + return objects; + } + + @Override + public void create(UUID id, String type, Map attributes) + { + } + + @Override + public void configureMessageStore(VirtualHost virtualHost, MessageStoreRecoveryHandler recoveryHandler, + TransactionLogRecoveryHandler tlogRecoveryHandler) throws Exception + { + } + + @Override + public void close() throws Exception + { + } + + @Override + public StoredMessage addMessage(T metaData) + { + return null; + } + + @Override + public boolean isPersistent() + { + return false; + } + + @Override + public Transaction newTransaction() + { + return null; + } + + @Override + public void activate() throws Exception + { + } + + @Override + public void addEventListener(EventListener eventListener, Event... events) + { + } + + @Override + public String getStoreLocation() + { + return null; + } + + @Override + public void onDelete() + { + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullUpgrader.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullUpgrader.java new file mode 100644 index 0000000000..c8a812fa89 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/NullUpgrader.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.store; + +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +public final class NullUpgrader implements DurableConfigurationStoreUpgrader +{ + private DurableConfigurationRecoverer _durableConfigurationRecoverer; + + public NullUpgrader(final DurableConfigurationRecoverer durableConfigurationRecoverer) + { + _durableConfigurationRecoverer = durableConfigurationRecoverer; + } + + @Override + public void configuredObject(final UUID id, final String type, final Map attributes) + { + _durableConfigurationRecoverer.onConfiguredObject(id, type, attributes); + } + + @Override + public void complete() + { + } + + @Override + public void setNextUpgrader(final DurableConfigurationStoreUpgrader upgrader) + { + throw new UnsupportedOperationException("NullUpgrader must always be the last upgrader"); + } + + @Override + public Map getUpdatedRecords() + { + return Collections.emptyMap(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/OperationalLoggingListener.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/OperationalLoggingListener.java new file mode 100644 index 0000000000..4ab1a3ab05 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/OperationalLoggingListener.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.store; + +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ConfigStoreMessages; +import org.apache.qpid.server.logging.messages.MessageStoreMessages; +import org.apache.qpid.server.logging.messages.TransactionLogMessages; + +public class OperationalLoggingListener implements EventListener +{ + protected final LogSubject _logSubject; + private MessageStore _store; + + private OperationalLoggingListener(final MessageStore store, LogSubject logSubject) + { + _logSubject = logSubject; + store.addEventListener(this, + Event.BEFORE_INIT, + Event.AFTER_INIT, + Event.BEFORE_ACTIVATE, + Event.AFTER_ACTIVATE, + Event.AFTER_CLOSE, + Event.PERSISTENT_MESSAGE_SIZE_OVERFULL, + Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL); + _store = store; + } + + public void event(Event event) + { + switch(event) + { + case BEFORE_INIT: + CurrentActor.get().message(_logSubject, ConfigStoreMessages.CREATED()); + break; + case AFTER_INIT: + CurrentActor.get().message(_logSubject, MessageStoreMessages.CREATED()); + CurrentActor.get().message(_logSubject, TransactionLogMessages.CREATED()); + String storeLocation = _store.getStoreLocation(); + if (storeLocation != null) + { + CurrentActor.get().message(_logSubject, MessageStoreMessages.STORE_LOCATION(storeLocation)); + } + break; + case BEFORE_ACTIVATE: + CurrentActor.get().message(_logSubject, MessageStoreMessages.RECOVERY_START()); + break; + case AFTER_ACTIVATE: + CurrentActor.get().message(_logSubject, MessageStoreMessages.RECOVERY_COMPLETE()); + break; + case AFTER_CLOSE: + CurrentActor.get().message(_logSubject,MessageStoreMessages.CLOSED()); + break; + case PERSISTENT_MESSAGE_SIZE_OVERFULL: + CurrentActor.get().message(_logSubject,MessageStoreMessages.OVERFULL()); + break; + case PERSISTENT_MESSAGE_SIZE_UNDERFULL: + CurrentActor.get().message(_logSubject,MessageStoreMessages.UNDERFULL()); + break; + + } + } + + public static void listen(final MessageStore store, LogSubject logSubject) + { + new OperationalLoggingListener(store, logSubject); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/State.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/State.java new file mode 100644 index 0000000000..1d0936cec4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/State.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.store; + +public enum State +{ + /** The initial state of the store. In practice, the store immediately transitions to the subsequent states. */ + INITIAL, + + INITIALISING, + /** + * The initial set-up of the store has completed. + * If the store is persistent, it has not yet loaded configuration from disk. + * + * From the point of view of the user, the store is essentially stopped. + */ + INITIALISED, + + ACTIVATING, + ACTIVE, + + CLOSING, + CLOSED, + + QUIESCING, + /** The virtual host (and implicitly also the store) has been manually paused by the user to allow configuration changes to take place */ + QUIESCED; + +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StateManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StateManager.java new file mode 100644 index 0000000000..613b329beb --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StateManager.java @@ -0,0 +1,150 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 java.util.EnumMap; +import java.util.Map; + +import org.apache.qpid.server.store.StateManager.Transition; + +public class StateManager +{ + private State _state = State.INITIAL; + private EventListener _eventListener; + + private static final Map> _validTransitions = new EnumMap>(State.class); + + + static class Transition + { + private final Event _event; + private final State _endState; + private final State _startState; + + public Transition(State startState, State endState, Event event) + { + _event = event; + _startState = startState; + _endState = endState; + + Map stateTransitions = _validTransitions.get(startState); + if(stateTransitions == null) + { + stateTransitions = new EnumMap(State.class); + _validTransitions.put(startState, stateTransitions); + } + stateTransitions.put(endState, this); + } + + public Event getEvent() + { + return _event; + } + + public State getStartState() + { + return _startState; + } + + public State getEndState() + { + return _endState; + } + + } + + public static final Transition INITIALISE = new Transition(State.INITIAL, State.INITIALISING, Event.BEFORE_INIT); + public static final Transition INITALISE_COMPLETE = new Transition(State.INITIALISING, State.INITIALISED, Event.AFTER_INIT); + + public static final Transition ACTIVATE = new Transition(State.INITIALISED, State.ACTIVATING, Event.BEFORE_ACTIVATE); + public static final Transition ACTIVATE_COMPLETE = new Transition(State.ACTIVATING, State.ACTIVE, Event.AFTER_ACTIVATE); + + public static final Transition CLOSE_INITIALISED = new Transition(State.INITIALISED, State.CLOSING, Event.BEFORE_CLOSE);; + public static final Transition CLOSE_ACTIVE = new Transition(State.ACTIVE, State.CLOSING, Event.BEFORE_CLOSE); + public static final Transition CLOSE_QUIESCED = new Transition(State.QUIESCED, State.CLOSING, Event.BEFORE_CLOSE); + public static final Transition CLOSE_COMPLETE = new Transition(State.CLOSING, State.CLOSED, Event.AFTER_CLOSE); + + public static final Transition PASSIVATE = new Transition(State.ACTIVE, State.INITIALISED, Event.BEFORE_PASSIVATE); + + public static final Transition QUIESCE = new Transition(State.ACTIVE, State.QUIESCING, Event.BEFORE_QUIESCE); + public static final Transition QUIESCE_COMPLETE = new Transition(State.QUIESCING, State.QUIESCED, Event.AFTER_QUIESCE); + + public static final Transition RESTART = new Transition(State.QUIESCED, State.ACTIVATING, Event.BEFORE_RESTART); + + + public StateManager(final EventManager eventManager) + { + this(new EventListener() + { + @Override + public void event(Event event) + { + eventManager.notifyEvent(event); + } + }); + } + + + public StateManager(EventListener eventListener) + { + _eventListener = eventListener; + } + + public synchronized State getState() + { + return _state; + } + + public synchronized void attainState(State desired) + { + Transition transition = null; + final Map stateTransitionMap = _validTransitions.get(_state); + if(stateTransitionMap != null) + { + transition = stateTransitionMap.get(desired); + } + if(transition == null) + { + throw new IllegalStateException("No valid transition from state " + _state + " to state " + desired); + } + _state = desired; + _eventListener.event(transition.getEvent()); + } + + public synchronized boolean isInState(State testedState) + { + return _state.equals(testedState); + } + + public synchronized boolean isNotInState(State testedState) + { + return !isInState(testedState); + } + + public synchronized void checkInState(State checkedState) + { + if (isNotInState(checkedState)) + { + throw new IllegalStateException("Unexpected state. Was : " + _state + " but expected : " + checkedState); + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StorableMessageMetaData.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StorableMessageMetaData.java new file mode 100755 index 0000000000..9ae6cca8e6 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StorableMessageMetaData.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.store; + +import java.nio.ByteBuffer; +import org.apache.qpid.server.plugin.MessageMetaDataType; + +public interface StorableMessageMetaData +{ + MessageMetaDataType getType(); + + int getStorableSize(); + + int writeToBuffer(int offsetInMetaData, ByteBuffer dest); + + int getContentSize(); + + boolean isPersistent(); +} + diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoreContext.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoreContext.java new file mode 100644 index 0000000000..88cc68bc71 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoreContext.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.log4j.Logger; + +/** + * A context that the store can use to associate with a transactional context. For example, it could store + * some kind of txn id. + * + * @author Apache Software Foundation + */ +public class StoreContext +{ + private static final Logger _logger = Logger.getLogger(StoreContext.class); + + private String _name; + private Object _payload; + + + public StoreContext() + { + _name = "StoreContext"; + } + + public StoreContext(String name) + { + _name = name; + } + + public Object getPayload() + { + return _payload; + } + + public void setPayload(Object payload) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("public void setPayload(Object payload = " + payload + "): called"); + } + _payload = payload; + } + + /** + * Prints out the transactional context as a string, mainly for debugging purposes. + * + * @return The transactional context as a string. + */ + public String toString() + { + return "<_name = " + _name + ", _payload = " + _payload + ">"; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoreFuture.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoreFuture.java new file mode 100644 index 0000000000..7d3bf90a75 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoreFuture.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.store; + +public interface StoreFuture +{ + StoreFuture IMMEDIATE_FUTURE = new StoreFuture() + { + public boolean isComplete() + { + return true; + } + + public void waitForCompletion() + { + } + }; + + boolean isComplete(); + + void waitForCompletion(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoredMemoryMessage.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoredMemoryMessage.java new file mode 100755 index 0000000000..e7302270bb --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoredMemoryMessage.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.store; + +import java.nio.ByteBuffer; + +public class StoredMemoryMessage implements StoredMessage +{ + private final long _messageNumber; + private ByteBuffer _content; + private final StorableMessageMetaData _metaData; + + public StoredMemoryMessage(long messageNumber, StorableMessageMetaData metaData) + { + _messageNumber = messageNumber; + _metaData = metaData; + } + + public long getMessageNumber() + { + return _messageNumber; + } + + public void addContent(int offsetInMessage, ByteBuffer src) + { + if(_content == null) + { + if(offsetInMessage == 0) + { + _content = src.slice(); + } + else + { + final int contentSize = _metaData.getContentSize(); + int size = (contentSize < offsetInMessage + src.remaining()) + ? offsetInMessage + src.remaining() + : contentSize; + _content = ByteBuffer.allocate(size); + addContent(offsetInMessage, src); + } + } + else + { + if(_content.limit() >= offsetInMessage + src.remaining()) + { + _content.position(offsetInMessage); + _content.put(src); + _content.position(0); + } + else + { + final int contentSize = _metaData.getContentSize(); + int size = (contentSize < offsetInMessage + src.remaining()) + ? offsetInMessage + src.remaining() + : contentSize; + ByteBuffer oldContent = _content; + _content = ByteBuffer.allocate(size); + _content.put(oldContent); + _content.position(0); + addContent(offsetInMessage, src); + } + + } + } + + public int getContent(int offset, ByteBuffer dst) + { + if(_content == null) + { + return 0; + } + ByteBuffer src = _content.duplicate(); + + int oldPosition = src.position(); + + src.position(oldPosition + offset); + + int length = dst.remaining() < src.remaining() ? dst.remaining() : src.remaining(); + src.limit(oldPosition + length); + + dst.put(src); + + + return length; + } + + + public ByteBuffer getContent(int offsetInMessage, int size) + { + if(_content == null) + { + return null; + } + ByteBuffer buf = _content.duplicate(); + + if(offsetInMessage != 0) + { + buf.position(offsetInMessage); + buf = buf.slice(); + } + + buf.limit(size); + return buf; + } + + public StoreFuture flushToStore() + { + return StoreFuture.IMMEDIATE_FUTURE; + } + + + public StorableMessageMetaData getMetaData() + { + return _metaData; + } + + public void remove() + { + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoredMessage.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoredMessage.java new file mode 100755 index 0000000000..7909003855 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/StoredMessage.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.store; + +import java.nio.ByteBuffer; + +public interface StoredMessage +{ + M getMetaData(); + + public long getMessageNumber(); + + void addContent(int offsetInMessage, ByteBuffer src); + + int getContent(int offsetInMessage, ByteBuffer dst); + + ByteBuffer getContent(int offsetInMessage, int size); + + StoreFuture flushToStore(); + + void remove(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/Transaction.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/Transaction.java new file mode 100644 index 0000000000..ed6b89e373 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/Transaction.java @@ -0,0 +1,81 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.message.EnqueableMessage; + +public interface Transaction +{ + /** + * Places a message onto a specified queue, in a given transactional context. + * + * + * + * @param queue The queue to place the message on. + * @param message + * @throws org.apache.qpid.AMQStoreException If the operation fails for any reason. + */ + void enqueueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException; + + /** + * Extracts a message from a specified queue, in a given transactional context. + * + * @param queue The queue to place the message on. + * @param message The message to dequeue. + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + void dequeueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException; + + + /** + * Commits all operations performed within a given transactional context. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void commitTran() throws AMQStoreException; + + /** + * Commits all operations performed within a given transactional context. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + StoreFuture commitTranAsync() throws AMQStoreException; + + /** + * Abandons all operations performed within a given transactional context. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void abortTran() throws AMQStoreException; + + + public static interface Record + { + TransactionLogResource getQueue(); + EnqueableMessage getMessage(); + } + + void removeXid(long format, byte[] globalId, byte[] branchId) throws AMQStoreException; + + void recordXid(long format, byte[] globalId, byte[] branchId, Transaction.Record[] enqueues, Transaction.Record[] dequeues) + throws AMQStoreException; +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLog.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLog.java new file mode 100755 index 0000000000..da7f8d18b2 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLog.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.store; + +public interface TransactionLog +{ + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLogRecoveryHandler.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLogRecoveryHandler.java new file mode 100755 index 0000000000..bd4da648f9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLogRecoveryHandler.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.store; + +import java.util.UUID; + +public interface TransactionLogRecoveryHandler +{ + QueueEntryRecoveryHandler begin(MessageStore log); + + public static interface QueueEntryRecoveryHandler + { + DtxRecordRecoveryHandler completeQueueEntryRecovery(); + + void queueEntry(UUID queueId, long messageId); + } + + public static interface DtxRecordRecoveryHandler + { + void dtxRecord(long format, byte[] globalId, byte[] branchId, Transaction.Record[] enqueues, Transaction.Record[] dequeues); + + void completeDtxRecordRecovery(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLogResource.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLogResource.java new file mode 100755 index 0000000000..576dca847d --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/TransactionLogResource.java @@ -0,0 +1,28 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.store; + +import java.util.UUID; + +public interface TransactionLogResource +{ + public UUID getId(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedDependency.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedDependency.java new file mode 100644 index 0000000000..98348efbd2 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedDependency.java @@ -0,0 +1,31 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +import java.util.UUID; + +public interface UnresolvedDependency +{ + public UUID getId(); + public String getType(); + + public void resolve(final T dependency); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedObject.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedObject.java new file mode 100644 index 0000000000..7ebebadae7 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UnresolvedObject.java @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +public interface UnresolvedObject +{ + public UnresolvedDependency[] getUnresolvedDependencies(); + + T resolve(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UpgraderProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UpgraderProvider.java new file mode 100644 index 0000000000..c2ea0745ff --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/UpgraderProvider.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.store; + +public interface UpgraderProvider +{ + DurableConfigurationStoreUpgrader getUpgrader(int configVersion, DurableConfigurationRecoverer recoverer); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/jdbc/ConnectionProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/jdbc/ConnectionProvider.java new file mode 100644 index 0000000000..54978776e7 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/store/jdbc/ConnectionProvider.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface ConnectionProvider +{ + Connection getConnection() throws SQLException; + + void close() throws SQLException; +} + diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/AssignedSubscriptionMessageGroupManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/AssignedSubscriptionMessageGroupManager.java new file mode 100644 index 0000000000..efedad1181 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/AssignedSubscriptionMessageGroupManager.java @@ -0,0 +1,158 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.subscription; + +import org.apache.qpid.server.queue.QueueEntryVisitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.qpid.server.queue.QueueEntry; + +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; + + +public class AssignedSubscriptionMessageGroupManager implements MessageGroupManager +{ + private static final Logger _logger = LoggerFactory.getLogger(AssignedSubscriptionMessageGroupManager.class); + + + private final String _groupId; + private final ConcurrentHashMap _groupMap = new ConcurrentHashMap(); + private final int _groupMask; + + public AssignedSubscriptionMessageGroupManager(final String groupId, final int maxGroups) + { + _groupId = groupId; + _groupMask = pow2(maxGroups)-1; + } + + private static int pow2(final int i) + { + int val = 1; + while(val < i) + { + val<<=1; + } + return val; + } + + public Subscription getAssignedSubscription(final QueueEntry entry) + { + Object groupVal = entry.getMessage().getMessageHeader().getHeader(_groupId); + return groupVal == null ? null : _groupMap.get(groupVal.hashCode() & _groupMask); + } + + public boolean acceptMessage(Subscription sub, QueueEntry entry) + { + Object groupVal = entry.getMessage().getMessageHeader().getHeader(_groupId); + if(groupVal == null) + { + return true; + } + else + { + Integer group = groupVal.hashCode() & _groupMask; + Subscription assignedSub = _groupMap.get(group); + if(assignedSub == sub) + { + return true; + } + else + { + if(assignedSub == null) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Assigning group " + groupVal + " to sub " + sub); + } + assignedSub = _groupMap.putIfAbsent(group, sub); + return assignedSub == null || assignedSub == sub; + } + else + { + return false; + } + } + } + } + + public QueueEntry findEarliestAssignedAvailableEntry(Subscription sub) + { + EntryFinder visitor = new EntryFinder(sub); + sub.getQueue().visit(visitor); + return visitor.getEntry(); + } + + private class EntryFinder implements QueueEntryVisitor + { + private QueueEntry _entry; + private Subscription _sub; + + public EntryFinder(final Subscription sub) + { + _sub = sub; + } + + public boolean visit(final QueueEntry entry) + { + if(!entry.isAvailable()) + { + return false; + } + + Object groupId = entry.getMessage().getMessageHeader().getHeader(_groupId); + if(groupId == null) + { + return false; + } + + Integer group = groupId.hashCode() & _groupMask; + Subscription assignedSub = _groupMap.get(group); + if(assignedSub == _sub) + { + _entry = entry; + return true; + } + else + { + return false; + } + } + + public QueueEntry getEntry() + { + return _entry; + } + } + + public void clearAssignments(Subscription sub) + { + Iterator subIter = _groupMap.values().iterator(); + while(subIter.hasNext()) + { + if(subIter.next() == sub) + { + subIter.remove(); + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java new file mode 100644 index 0000000000..632b59d3fa --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java @@ -0,0 +1,29 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.subscription; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.QueueEntry; + +public interface ClientDeliveryMethod +{ + void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag) throws AMQException; +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/DefinedGroupMessageGroupManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/DefinedGroupMessageGroupManager.java new file mode 100644 index 0000000000..f38e23b342 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/DefinedGroupMessageGroupManager.java @@ -0,0 +1,270 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.subscription; + +import org.apache.qpid.server.queue.QueueEntryVisitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.QueueEntry; + +import java.util.HashMap; +import java.util.Map; + +public class DefinedGroupMessageGroupManager implements MessageGroupManager +{ + private static final Logger _logger = LoggerFactory.getLogger(DefinedGroupMessageGroupManager.class); + + private final String _groupId; + private final String _defaultGroup; + private final Map _groupMap = new HashMap(); + private final SubscriptionResetHelper _resetHelper; + + private final class Group + { + private final Object _group; + private Subscription _subscription; + private int _activeCount; + + private Group(final Object key, final Subscription subscription) + { + _group = key; + _subscription = subscription; + } + + public boolean add() + { + if(_subscription != null) + { + _activeCount++; + return true; + } + else + { + return false; + } + } + + public void subtract() + { + if(--_activeCount == 0) + { + _resetHelper.resetSubPointersForGroups(_subscription, false); + _subscription = null; + _groupMap.remove(_group); + } + } + + @Override + public boolean equals(final Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + + Group group = (Group) o; + + return _group.equals(group._group); + } + + @Override + public int hashCode() + { + return _group.hashCode(); + } + + public boolean isValid() + { + return !(_subscription == null || (_activeCount == 0 && _subscription.isClosed())); + } + + public Subscription getSubscription() + { + return _subscription; + } + + @Override + public String toString() + { + return "Group{" + + "_group=" + _group + + ", _subscription=" + _subscription + + ", _activeCount=" + _activeCount + + '}'; + } + } + + public DefinedGroupMessageGroupManager(final String groupId, String defaultGroup, SubscriptionResetHelper resetHelper) + { + _groupId = groupId; + _defaultGroup = defaultGroup; + _resetHelper = resetHelper; + } + + public synchronized Subscription getAssignedSubscription(final QueueEntry entry) + { + Object groupId = getKey(entry); + + Group group = _groupMap.get(groupId); + return group == null || !group.isValid() ? null : group.getSubscription(); + } + + public synchronized boolean acceptMessage(final Subscription sub, final QueueEntry entry) + { + Object groupId = getKey(entry); + Group group = _groupMap.get(groupId); + + if(group == null || !group.isValid()) + { + group = new Group(groupId, sub); + + _groupMap.put(groupId, group); + + // there's a small change that the group became empty between the point at which getNextAvailable() was + // called on the subscription, and when accept message is called... in that case we want to avoid delivering + // out of order + if(_resetHelper.isEntryAheadOfSubscription(entry, sub)) + { + return false; + } + + } + + Subscription assignedSub = group.getSubscription(); + + if(assignedSub == sub) + { + entry.addStateChangeListener(new GroupStateChangeListener(group, entry)); + return true; + } + else + { + return false; + } + } + + + public synchronized QueueEntry findEarliestAssignedAvailableEntry(final Subscription sub) + { + EntryFinder visitor = new EntryFinder(sub); + sub.getQueue().visit(visitor); + return visitor.getEntry(); + } + + private class EntryFinder implements QueueEntryVisitor + { + private QueueEntry _entry; + private Subscription _sub; + + public EntryFinder(final Subscription sub) + { + _sub = sub; + } + + public boolean visit(final QueueEntry entry) + { + if(!entry.isAvailable()) + { + return false; + } + + Object groupId = getKey(entry); + + Group group = _groupMap.get(groupId); + if(group != null && group.getSubscription() == _sub) + { + _entry = entry; + return true; + } + else + { + return false; + } + } + + public QueueEntry getEntry() + { + return _entry; + } + } + + + public void clearAssignments(final Subscription sub) + { + } + + private Object getKey(QueueEntry entry) + { + ServerMessage message = entry.getMessage(); + AMQMessageHeader messageHeader = message == null ? null : message.getMessageHeader(); + Object groupVal = messageHeader == null ? _defaultGroup : messageHeader.getHeader(_groupId); + if(groupVal == null) + { + groupVal = _defaultGroup; + } + return groupVal; + } + + private class GroupStateChangeListener implements QueueEntry.StateChangeListener + { + private final Group _group; + + public GroupStateChangeListener(final Group group, + final QueueEntry entry) + { + _group = group; + } + + public void stateChanged(final QueueEntry entry, + final QueueEntry.State oldState, + final QueueEntry.State newState) + { + synchronized (DefinedGroupMessageGroupManager.this) + { + if(_group.isValid()) + { + if(oldState != newState) + { + if(newState == QueueEntry.State.ACQUIRED) + { + _group.add(); + } + else if(oldState == QueueEntry.State.ACQUIRED) + { + _group.subtract(); + } + } + } + else + { + entry.removeStateChangeListener(this); + } + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/MessageGroupManager.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/MessageGroupManager.java new file mode 100644 index 0000000000..8ce4ce3344 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/MessageGroupManager.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.subscription; + +import org.apache.qpid.server.queue.QueueEntry; + +public interface MessageGroupManager +{ + public interface SubscriptionResetHelper + { + public void resetSubPointersForGroups(Subscription subscription, boolean clearAssignments); + + boolean isEntryAheadOfSubscription(QueueEntry entry, Subscription sub); + } + + Subscription getAssignedSubscription(QueueEntry entry); + + boolean acceptMessage(Subscription sub, QueueEntry entry); + + QueueEntry findEarliestAssignedAvailableEntry(Subscription sub); + + void clearAssignments(Subscription sub); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java new file mode 100644 index 0000000000..e2ed4104de --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java @@ -0,0 +1,28 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.subscription; + +import org.apache.qpid.server.queue.QueueEntry; + +public interface RecordDeliveryMethod +{ + void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/Subscription.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/Subscription.java new file mode 100644 index 0000000000..36d49d8279 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/Subscription.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.subscription; + +import java.util.concurrent.atomic.AtomicLong; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; + +public interface Subscription +{ + AtomicLong SUB_ID_GENERATOR = new AtomicLong(0); + + LogActor getLogActor(); + + boolean isTransient(); + + long getBytesOut(); + + long getMessagesOut(); + + long getUnacknowledgedBytes(); + + long getUnacknowledgedMessages(); + + public static enum State + { + ACTIVE, + SUSPENDED, + CLOSED + } + + public static interface StateListener + { + public void stateChange(Subscription sub, State oldState, State newState); + } + + AMQQueue getQueue(); + AMQSessionModel getSessionModel(); + + QueueEntry.SubscriptionAcquiredState getOwningState(); + + void setQueue(AMQQueue queue, boolean exclusive); + + void setNoLocal(boolean noLocal); + + long getSubscriptionID(); + + boolean isSuspended(); + + boolean hasInterest(QueueEntry msg); + + boolean isClosed(); + + boolean acquires(); + + boolean seesRequeues(); + + void close(); + + void send(QueueEntry entry, boolean batch) throws AMQException; + + void flushBatched(); + + void queueDeleted(AMQQueue queue); + + + boolean wouldSuspend(QueueEntry msg); + + boolean trySendLock(); + + + void getSendLock(); + + void releaseSendLock(); + + void releaseQueueEntry(final QueueEntry queueEntryImpl); + + void onDequeue(final QueueEntry queueEntry); + + void restoreCredit(final QueueEntry queueEntry); + + void setStateListener(final StateListener listener); + + public State getState(); + + AMQQueue.Context getQueueContext(); + + void setQueueContext(AMQQueue.Context queueContext); + + + boolean isActive(); + + public void set(String key, Object value); + + public Object get(String key); + + boolean isSessionTransactional(); + + void queueEmpty() throws AMQException; + + String getConsumerName(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java new file mode 100644 index 0000000000..bf5ce31bd9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java @@ -0,0 +1,280 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.subscription; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public class SubscriptionList +{ + private final SubscriptionNode _head = new SubscriptionNode(); + + private final AtomicReference _tail = new AtomicReference(_head); + private final AtomicReference _subNodeMarker = new AtomicReference(_head); + private final AtomicInteger _size = new AtomicInteger(); + + public static final class SubscriptionNode + { + private final AtomicBoolean _deleted = new AtomicBoolean(); + private final AtomicReference _next = new AtomicReference(); + private final Subscription _sub; + + public SubscriptionNode() + { + //used for sentinel head and dummy node construction + _sub = null; + _deleted.set(true); + } + + public SubscriptionNode(final Subscription sub) + { + //used for regular node construction + _sub = sub; + } + + /** + * Retrieves the first non-deleted node following the current node. + * Any deleted non-tail nodes encountered during the search are unlinked. + * + * @return the next non-deleted node, or null if none was found. + */ + public SubscriptionNode findNext() + { + SubscriptionNode next = nextNode(); + while(next != null && next.isDeleted()) + { + final SubscriptionNode newNext = next.nextNode(); + if(newNext != null) + { + //try to move our _next reference forward to the 'newNext' + //node to unlink the deleted node + _next.compareAndSet(next, newNext); + next = nextNode(); + } + else + { + //'newNext' is null, meaning 'next' is the current tail. Can't unlink + //the tail node for thread safety reasons, just use the null. + next = null; + } + } + + return next; + } + + /** + * Gets the immediately next referenced node in the structure. + * + * @return the immediately next node in the structure, or null if at the tail. + */ + protected SubscriptionNode nextNode() + { + return _next.get(); + } + + /** + * Used to initialise the 'next' reference. Will only succeed if the reference was not previously set. + * + * @param node the SubscriptionNode to set as 'next' + * @return whether the operation succeeded + */ + private boolean setNext(final SubscriptionNode node) + { + return _next.compareAndSet(null, node); + } + + public boolean isDeleted() + { + return _deleted.get(); + } + + public boolean delete() + { + return _deleted.compareAndSet(false,true); + } + + public Subscription getSubscription() + { + return _sub; + } + } + + private void insert(final SubscriptionNode node, final boolean count) + { + for (;;) + { + SubscriptionNode tail = _tail.get(); + SubscriptionNode next = tail.nextNode(); + if (tail == _tail.get()) + { + if (next == null) + { + if (tail.setNext(node)) + { + _tail.compareAndSet(tail, node); + if(count) + { + _size.incrementAndGet(); + } + return; + } + } + else + { + _tail.compareAndSet(tail, next); + } + } + } + } + + public void add(final Subscription sub) + { + SubscriptionNode node = new SubscriptionNode(sub); + insert(node, true); + } + + public boolean remove(final Subscription sub) + { + SubscriptionNode prevNode = _head; + SubscriptionNode node = _head.nextNode(); + + while(node != null) + { + if(sub.equals(node.getSubscription()) && node.delete()) + { + _size.decrementAndGet(); + + SubscriptionNode tail = _tail.get(); + if(node == tail) + { + //we cant remove the last node from the structure for + //correctness reasons, however we have just 'deleted' + //the tail. Inserting an empty dummy node after it will + //let us scavenge the node containing the Subscription. + insert(new SubscriptionNode(), false); + } + + //advance the next node reference in the 'prevNode' to scavange + //the newly 'deleted' node for the Subscription. + prevNode.findNext(); + + nodeMarkerCleanup(node); + + return true; + } + + prevNode = node; + node = node.findNext(); + } + + return false; + } + + private void nodeMarkerCleanup(final SubscriptionNode node) + { + SubscriptionNode markedNode = _subNodeMarker.get(); + if(node == markedNode) + { + //if the marked node is the one we are removing, then + //replace it with a dummy pointing at the next node. + //this is OK as the marked node is only used to index + //into the list and find the next node to use. + //Because we inserted a dummy if node was the + //tail, markedNode.nextNode() can never be null. + SubscriptionNode dummy = new SubscriptionNode(); + dummy.setNext(markedNode.nextNode()); + + //if the CAS fails the marked node has changed, thus + //we don't care about the dummy and just forget it + _subNodeMarker.compareAndSet(markedNode, dummy); + } + else if(markedNode != null) + { + //if the marked node was already deleted then it could + //hold subsequently removed nodes after it in the list + //in memory. Scavenge it to ensure their actual removal. + if(markedNode != _head && markedNode.isDeleted()) + { + markedNode.findNext(); + } + } + } + + public boolean updateMarkedNode(final SubscriptionNode expected, final SubscriptionNode nextNode) + { + return _subNodeMarker.compareAndSet(expected, nextNode); + } + + /** + * Get the current marked SubscriptionNode. This should only be used only to index into the list and find the next node + * after the mark, since if the previously marked node was subsequently deleted the item returned may be a dummy node + * with reference to the next node. + * + * @return the previously marked node (or a dummy if it was subsequently deleted) + */ + public SubscriptionNode getMarkedNode() + { + return _subNodeMarker.get(); + } + + + public static class SubscriptionNodeIterator + { + private SubscriptionNode _lastNode; + + SubscriptionNodeIterator(SubscriptionNode startNode) + { + _lastNode = startNode; + } + + public SubscriptionNode getNode() + { + return _lastNode; + } + + public boolean advance() + { + SubscriptionNode nextNode = _lastNode.findNext(); + _lastNode = nextNode; + + return _lastNode != null; + } + } + + public SubscriptionNodeIterator iterator() + { + return new SubscriptionNodeIterator(_head); + } + + public SubscriptionNode getHead() + { + return _head; + } + + public int size() + { + return _size.get(); + } +} + + + diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AlreadyKnownDtxException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AlreadyKnownDtxException.java new file mode 100644 index 0000000000..faa4ec592f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AlreadyKnownDtxException.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.txn; + +import org.apache.qpid.transport.Xid; + +public class AlreadyKnownDtxException extends DtxException +{ + public AlreadyKnownDtxException(Xid id) + { + super("Xid " + id + " cannot be started as it is already known"); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AsyncAutoCommitTransaction.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AsyncAutoCommitTransaction.java new file mode 100755 index 0000000000..31e4dc6def --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AsyncAutoCommitTransaction.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.txn; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.store.Transaction; + +import java.util.Collection; +import java.util.List; + +/** + * An implementation of ServerTransaction where each enqueue/dequeue + * operation takes place within it own transaction. + * + * Since there is no long-lived transaction, the commit and rollback methods of + * this implementation are empty. + */ +public class AsyncAutoCommitTransaction implements ServerTransaction +{ + static final String QPID_STRICT_ORDER_WITH_MIXED_DELIVERY_MODE = "qpid.strict_order_with_mixed_delivery_mode"; + + protected static final Logger _logger = Logger.getLogger(AsyncAutoCommitTransaction.class); + + private final MessageStore _messageStore; + private final FutureRecorder _futureRecorder; + + //Set true to ensure strict ordering when enqueing messages with mixed delivery mode, i.e. disable async persistence + private boolean _strictOrderWithMixedDeliveryMode = Boolean.getBoolean(QPID_STRICT_ORDER_WITH_MIXED_DELIVERY_MODE); + + public static interface FutureRecorder + { + public void recordFuture(StoreFuture future, Action action); + + } + + public AsyncAutoCommitTransaction(MessageStore transactionLog, FutureRecorder recorder) + { + _messageStore = transactionLog; + _futureRecorder = recorder; + } + + @Override + public long getTransactionStartTime() + { + return 0L; + } + + @Override + public long getTransactionUpdateTime() + { + return 0L; + } + + /** + * Since AutoCommitTransaction have no concept of a long lived transaction, any Actions registered + * by the caller are executed immediately. + */ + public void addPostTransactionAction(final Action immediateAction) + { + addFuture(StoreFuture.IMMEDIATE_FUTURE, immediateAction); + + } + + public void dequeue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + Transaction txn = null; + try + { + StoreFuture future; + if(message.isPersistent() && queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeue of message number " + message.getMessageNumber() + " from transaction log. Queue : " + queue.getName()); + } + + txn = _messageStore.newTransaction(); + txn.dequeueMessage(queue, message); + future = txn.commitTranAsync(); + + txn = null; + } + else + { + future = StoreFuture.IMMEDIATE_FUTURE; + } + addFuture(future, postTransactionAction); + postTransactionAction = null; + } + catch (AMQException e) + { + _logger.error("Error during message dequeue", e); + throw new RuntimeException("Error during message dequeue", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + } + + private void addFuture(final StoreFuture future, final Action action) + { + if(action != null) + { + if(future.isComplete()) + { + action.postCommit(); + } + else + { + _futureRecorder.recordFuture(future, action); + } + } + } + + private void addEnqueueFuture(final StoreFuture future, final Action action, boolean persistent) + { + if(action != null) + { + // For persistent messages, do not synchronously invoke postCommit even if the future is completed. + // Otherwise, postCommit (which actually does the enqueuing) might be called on successive messages out of order. + if(future.isComplete() && !persistent && !_strictOrderWithMixedDeliveryMode) + { + action.postCommit(); + } + else + { + _futureRecorder.recordFuture(future, action); + } + } + } + + public void dequeue(Collection queueEntries, Action postTransactionAction) + { + Transaction txn = null; + try + { + for(QueueEntry entry : queueEntries) + { + ServerMessage message = entry.getMessage(); + BaseQueue queue = entry.getQueue(); + + if(message.isPersistent() && queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeue of message number " + message.getMessageNumber() + " from transaction log. Queue : " + queue.getName()); + } + + if(txn == null) + { + txn = _messageStore.newTransaction(); + } + + txn.dequeueMessage(queue, message); + } + + } + StoreFuture future; + if(txn != null) + { + future = txn.commitTranAsync(); + txn = null; + } + else + { + future = StoreFuture.IMMEDIATE_FUTURE; + } + addFuture(future, postTransactionAction); + postTransactionAction = null; + } + catch (AMQException e) + { + _logger.error("Error during message dequeues", e); + throw new RuntimeException("Error during message dequeues", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + } + + + public void enqueue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + Transaction txn = null; + try + { + StoreFuture future; + if(message.isPersistent() && queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueue of message number " + message.getMessageNumber() + " to transaction log. Queue : " + queue.getName()); + } + + txn = _messageStore.newTransaction(); + txn.enqueueMessage(queue, message); + future = txn.commitTranAsync(); + txn = null; + } + else + { + future = StoreFuture.IMMEDIATE_FUTURE; + } + addEnqueueFuture(future, postTransactionAction, message.isPersistent()); + postTransactionAction = null; + } + catch (AMQException e) + { + _logger.error("Error during message enqueue", e); + throw new RuntimeException("Error during message enqueue", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + + } + + public void enqueue(List queues, EnqueableMessage message, Action postTransactionAction) + { + Transaction txn = null; + try + { + + if(message.isPersistent()) + { + for(BaseQueue queue : queues) + { + if (queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueue of message number " + message.getMessageNumber() + " to transaction log. Queue : " + queue.getName()); + } + if (txn == null) + { + txn = _messageStore.newTransaction(); + } + + txn.enqueueMessage(queue, message); + + + } + } + + } + StoreFuture future; + if (txn != null) + { + future = txn.commitTranAsync(); + txn = null; + } + else + { + future = StoreFuture.IMMEDIATE_FUTURE; + } + addEnqueueFuture(future, postTransactionAction, message.isPersistent()); + postTransactionAction = null; + + + } + catch (AMQException e) + { + _logger.error("Error during message enqueues", e); + throw new RuntimeException("Error during message enqueues", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + } + + + public void commit(final Runnable immediatePostTransactionAction) + { + if(immediatePostTransactionAction != null) + { + addFuture(StoreFuture.IMMEDIATE_FUTURE, new Action() + { + public void postCommit() + { + immediatePostTransactionAction.run(); + } + + public void onRollback() + { + } + }); + } + } + + public void commit() + { + } + + public void rollback() + { + } + + public boolean isTransactional() + { + return false; + } + + private void rollbackIfNecessary(Action postTransactionAction, Transaction txn) + { + if (txn != null) + { + try + { + txn.abortTran(); + } + catch (AMQStoreException e) + { + _logger.error("Abort transaction failed", e); + // Deliberate decision not to re-throw this exception. Rationale: we can only reach here if a previous + // TransactionLog method has ended in Exception. If we were to re-throw here, we would prevent + // our caller from receiving the original exception (which is likely to be more revealing of the underlying error). + } + } + if (postTransactionAction != null) + { + postTransactionAction.onRollback(); + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AutoCommitTransaction.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AutoCommitTransaction.java new file mode 100755 index 0000000000..b081641f47 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/AutoCommitTransaction.java @@ -0,0 +1,282 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.AMQStoreException; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.Transaction; + +import java.util.Collection; +import java.util.List; + +/** + * An implementation of ServerTransaction where each enqueue/dequeue + * operation takes place within it own transaction. + * + * Since there is no long-lived transaction, the commit and rollback methods of + * this implementation are empty. + */ +public class AutoCommitTransaction implements ServerTransaction +{ + protected static final Logger _logger = Logger.getLogger(AutoCommitTransaction.class); + + private final MessageStore _messageStore; + + public AutoCommitTransaction(MessageStore transactionLog) + { + _messageStore = transactionLog; + } + + @Override + public long getTransactionStartTime() + { + return 0L; + } + + @Override + public long getTransactionUpdateTime() + { + return 0L; + } + + /** + * Since AutoCommitTransaction have no concept of a long lived transaction, any Actions registered + * by the caller are executed immediately. + */ + public void addPostTransactionAction(final Action immediateAction) + { + immediateAction.postCommit(); + } + + public void dequeue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + Transaction txn = null; + try + { + if(message.isPersistent() && queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeue of message number " + message.getMessageNumber() + " from transaction log. Queue : " + queue.getName()); + } + + txn = _messageStore.newTransaction(); + txn.dequeueMessage(queue, message); + txn.commitTran(); + txn = null; + } + postTransactionAction.postCommit(); + postTransactionAction = null; + } + catch (AMQException e) + { + _logger.error("Error during message dequeue", e); + throw new RuntimeException("Error during message dequeue", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + } + + public void dequeue(Collection queueEntries, Action postTransactionAction) + { + Transaction txn = null; + try + { + for(QueueEntry entry : queueEntries) + { + ServerMessage message = entry.getMessage(); + BaseQueue queue = entry.getQueue(); + + if(message.isPersistent() && queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeue of message number " + message.getMessageNumber() + " from transaction log. Queue : " + queue.getName()); + } + + if(txn == null) + { + txn = _messageStore.newTransaction(); + } + + txn.dequeueMessage(queue, message); + } + + } + if(txn != null) + { + txn.commitTran(); + txn = null; + } + postTransactionAction.postCommit(); + postTransactionAction = null; + } + catch (AMQException e) + { + _logger.error("Error during message dequeues", e); + throw new RuntimeException("Error during message dequeues", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + } + + + public void enqueue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + Transaction txn = null; + try + { + if(message.isPersistent() && queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueue of message number " + message.getMessageNumber() + " to transaction log. Queue : " + queue.getName()); + } + + txn = _messageStore.newTransaction(); + txn.enqueueMessage(queue, message); + txn.commitTran(); + txn = null; + } + postTransactionAction.postCommit(); + postTransactionAction = null; + } + catch (AMQException e) + { + _logger.error("Error during message enqueue", e); + throw new RuntimeException("Error during message enqueue", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + + } + + public void enqueue(List queues, EnqueableMessage message, Action postTransactionAction) + { + Transaction txn = null; + try + { + + if(message.isPersistent()) + { + for(BaseQueue queue : queues) + { + if (queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueue of message number " + message.getMessageNumber() + " to transaction log. Queue : " + queue.getName()); + } + if (txn == null) + { + txn = _messageStore.newTransaction(); + } + + txn.enqueueMessage(queue, message); + + + } + } + + } + if (txn != null) + { + txn.commitTran(); + txn = null; + } + + postTransactionAction.postCommit(); + postTransactionAction = null; + + + } + catch (AMQException e) + { + _logger.error("Error during message enqueues", e); + throw new RuntimeException("Error during message enqueues", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + } + + + public void commit(final Runnable immediatePostTransactionAction) + { + immediatePostTransactionAction.run(); + } + + public void commit() + { + } + + public void rollback() + { + } + + public boolean isTransactional() + { + return false; + } + + private void rollbackIfNecessary(Action postTransactionAction, Transaction txn) + { + if (txn != null) + { + try + { + txn.abortTran(); + } + catch (AMQStoreException e) + { + _logger.error("Abort transaction failed", e); + // Deliberate decision not to re-throw this exception. Rationale: we can only reach here if a previous + // TransactionLog method has ended in Exception. If we were to re-throw here, we would prevent + // our caller from receiving the original exception (which is likely to be more revealing of the underlying error). + } + } + if (postTransactionAction != null) + { + postTransactionAction.onRollback(); + } + } + + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DistributedTransaction.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DistributedTransaction.java new file mode 100644 index 0000000000..ab987f0fb9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DistributedTransaction.java @@ -0,0 +1,249 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.server.message.EnqueableMessage; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.Xid; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class DistributedTransaction implements ServerTransaction +{ + + private final AutoCommitTransaction _autoCommitTransaction; + + private DtxBranch _branch; + private AMQSessionModel _session; + private VirtualHost _vhost; + + + public DistributedTransaction(AMQSessionModel session, MessageStore store, VirtualHost vhost) + { + _session = session; + _vhost = vhost; + _autoCommitTransaction = new AutoCommitTransaction(vhost.getMessageStore()); + } + + @Override + public long getTransactionStartTime() + { + return 0; + } + + @Override + public long getTransactionUpdateTime() + { + return 0; + } + + public void addPostTransactionAction(Action postTransactionAction) + { + if(_branch != null) + { + _branch.addPostTransactionAcion(postTransactionAction); + } + else + { + _autoCommitTransaction.addPostTransactionAction(postTransactionAction); + } + } + + public void dequeue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + if(_branch != null) + { + _branch.dequeue(queue, message); + _branch.addPostTransactionAcion(postTransactionAction); + } + else + { + _autoCommitTransaction.dequeue(queue, message, postTransactionAction); + } + } + + public void dequeue(Collection messages, Action postTransactionAction) + { + if(_branch != null) + { + for(QueueEntry entry : messages) + { + _branch.dequeue(entry.getQueue(), entry.getMessage()); + } + _branch.addPostTransactionAcion(postTransactionAction); + } + else + { + _autoCommitTransaction.dequeue(messages, postTransactionAction); + } + } + + public void enqueue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + if(_branch != null) + { + _branch.enqueue(queue, message); + _branch.addPostTransactionAcion(postTransactionAction); + enqueue(Collections.singletonList(queue), message, postTransactionAction); + } + else + { + _autoCommitTransaction.enqueue(queue, message, postTransactionAction); + } + } + + public void enqueue(List queues, EnqueableMessage message, + Action postTransactionAction) + { + if(_branch != null) + { + for(BaseQueue queue : queues) + { + _branch.enqueue(queue, message); + } + _branch.addPostTransactionAcion(postTransactionAction); + } + else + { + _autoCommitTransaction.enqueue(queues, message, postTransactionAction); + } + } + + public void commit() + { + throw new IllegalStateException("Cannot call tx.commit() on a distributed transaction"); + } + + public void commit(Runnable immediatePostTransactionAction) + { + throw new IllegalStateException("Cannot call tx.commit() on a distributed transaction"); + } + + public void rollback() + { + throw new IllegalStateException("Cannot call tx.rollback() on a distributed transaction"); + } + + public boolean isTransactional() + { + return _branch != null; + } + + public void start(Xid id, boolean join, boolean resume) + throws UnknownDtxBranchException, AlreadyKnownDtxException, JoinAndResumeDtxException + { + if(join && resume) + { + throw new JoinAndResumeDtxException(id); + } + + DtxBranch branch = _vhost.getDtxRegistry().getBranch(id); + + if(branch == null) + { + if(join || resume) + { + throw new UnknownDtxBranchException(id); + } + branch = new DtxBranch(id,_vhost.getMessageStore(), _vhost); + if(_vhost.getDtxRegistry().registerBranch(branch)) + { + _branch = branch; + branch.associateSession(_session); + } + else + { + throw new AlreadyKnownDtxException(id); + } + } + else + { + if(join) + { + branch.associateSession(_session); + } + else if(resume) + { + branch.resumeSession(_session); + } + else + { + throw new AlreadyKnownDtxException(id); + } + _branch = branch; + } + } + + public void end(Xid id, boolean fail, boolean suspend) + throws UnknownDtxBranchException, NotAssociatedDtxException, SuspendAndFailDtxException, TimeoutDtxException + { + DtxBranch branch = _vhost.getDtxRegistry().getBranch(id); + + if(suspend && fail) + { + branch.disassociateSession(_session); + _branch = null; + throw new SuspendAndFailDtxException(id); + } + + + if(branch == null) + { + throw new UnknownDtxBranchException(id); + } + else + { + if(!branch.isAssociated(_session)) + { + throw new NotAssociatedDtxException(id); + } + if(branch.expired() || branch.getState() == DtxBranch.State.TIMEDOUT) + { + branch.disassociateSession(_session); + throw new TimeoutDtxException(id); + } + + if(suspend) + { + branch.suspendSession(_session); + } + else + { + if(fail) + { + branch.setState(DtxBranch.State.ROLLBACK_ONLY); + } + branch.disassociateSession(_session); + } + + _branch = null; + + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxBranch.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxBranch.java new file mode 100644 index 0000000000..fb32b654ce --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxBranch.java @@ -0,0 +1,413 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.txn; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.Transaction; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.Xid; + +public class DtxBranch +{ + private static final Logger _logger = Logger.getLogger(DtxBranch.class); + + private final Xid _xid; + private final List _postTransactionActions = new ArrayList(); + private State _state = State.ACTIVE; + private long _timeout; + private Map _associatedSessions = new HashMap(); + private final List _enqueueRecords = new ArrayList(); + private final List _dequeueRecords = new ArrayList(); + + private Transaction _transaction; + private long _expiration; + private VirtualHost _vhost; + private ScheduledFuture _timeoutFuture; + private MessageStore _store; + + + public enum State + { + ACTIVE, + PREPARED, + TIMEDOUT, + SUSPENDED, + FORGOTTEN, + HEUR_COM, + HEUR_RB, + ROLLBACK_ONLY + } + + + public DtxBranch(Xid xid, MessageStore store, VirtualHost vhost) + { + _xid = xid; + _store = store; + _vhost = vhost; + } + + public Xid getXid() + { + return _xid; + } + + public State getState() + { + return _state; + } + + public void setState(State state) + { + _state = state; + } + + public long getTimeout() + { + return _timeout; + } + + public void setTimeout(long timeout) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Setting timeout to " + timeout + "s for DtxBranch " + _xid); + } + + if(_timeoutFuture != null) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Attempting to cancel previous timeout task future for DtxBranch " + _xid); + } + + boolean succeeded = _timeoutFuture.cancel(false); + + if(_logger.isDebugEnabled()) + { + _logger.debug("Cancelling previous timeout task " + (succeeded ? "succeeded" : "failed") + + " for DtxBranch " + _xid); + } + } + + _timeout = timeout; + _expiration = timeout == 0 ? 0 : System.currentTimeMillis() + (1000 * timeout); + + if(_timeout == 0) + { + _timeoutFuture = null; + } + else + { + long delay = 1000*_timeout; + + if(_logger.isDebugEnabled()) + { + _logger.debug("Scheduling timeout and rollback after " + delay/1000 + + "s for DtxBranch " + _xid); + } + + _timeoutFuture = _vhost.scheduleTask(delay, new Runnable() + { + public void run() + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Timing out DtxBranch " + _xid); + } + + setState(State.TIMEDOUT); + try + { + rollback(); + } + catch (AMQStoreException e) + { + _logger.error("Unexpected error when attempting to rollback DtxBranch "+ _xid + " due to timeout", e); + throw new RuntimeException(e); + } + } + }); + } + } + + public boolean expired() + { + return _timeout != 0 && _expiration < System.currentTimeMillis(); + } + + public synchronized boolean isAssociated(AMQSessionModel session) + { + return _associatedSessions.containsKey(session); + } + + public synchronized boolean hasAssociatedSessions() + { + return !_associatedSessions.isEmpty(); + } + + + public synchronized boolean hasAssociatedActiveSessions() + { + if(hasAssociatedSessions()) + { + for(State state : _associatedSessions.values()) + { + if(state != State.SUSPENDED) + { + return true; + } + } + } + return false; + } + + public synchronized void clearAssociations() + { + _associatedSessions.clear(); + } + + synchronized boolean associateSession(AMQSessionModel associatedSession) + { + return _associatedSessions.put(associatedSession, State.ACTIVE) != null; + } + + synchronized boolean disassociateSession(AMQSessionModel associatedSession) + { + return _associatedSessions.remove(associatedSession) != null; + } + + public synchronized boolean resumeSession(AMQSessionModel session) + { + if(_associatedSessions.containsKey(session) && _associatedSessions.get(session) == State.SUSPENDED) + { + _associatedSessions.put(session, State.ACTIVE); + return true; + } + return false; + } + + public synchronized boolean suspendSession(AMQSessionModel session) + { + if(_associatedSessions.containsKey(session) && _associatedSessions.get(session) == State.ACTIVE) + { + _associatedSessions.put(session, State.SUSPENDED); + return true; + } + return false; + } + + public void prepare() throws AMQStoreException + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Performing prepare for DtxBranch " + _xid); + } + + Transaction txn = _store.newTransaction(); + txn.recordXid(_xid.getFormat(), + _xid.getGlobalId(), + _xid.getBranchId(), + _enqueueRecords.toArray(new Record[_enqueueRecords.size()]), + _dequeueRecords.toArray(new Record[_dequeueRecords.size()])); + txn.commitTran(); + + prePrepareTransaction(); + } + + public synchronized void rollback() throws AMQStoreException + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Performing rollback for DtxBranch " + _xid); + } + + if(_timeoutFuture != null) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Attempting to cancel previous timeout task future for DtxBranch " + _xid); + } + + boolean succeeded = _timeoutFuture.cancel(false); + _timeoutFuture = null; + + if(_logger.isDebugEnabled()) + { + _logger.debug("Cancelling previous timeout task " + (succeeded ? "succeeded" : "failed") + + " for DtxBranch " + _xid); + } + } + + if(_transaction != null) + { + // prepare has previously been called + + Transaction txn = _store.newTransaction(); + txn.removeXid(_xid.getFormat(), _xid.getGlobalId(), _xid.getBranchId()); + txn.commitTran(); + + _transaction.abortTran(); + } + + for(ServerTransaction.Action action : _postTransactionActions) + { + action.onRollback(); + } + _postTransactionActions.clear(); + } + + public void commit() throws AMQStoreException + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Performing commit for DtxBranch " + _xid); + } + + if(_timeoutFuture != null) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Attempting to cancel previous timeout task future for DtxBranch " + _xid); + } + + boolean succeeded = _timeoutFuture.cancel(false); + _timeoutFuture = null; + + if(_logger.isDebugEnabled()) + { + _logger.debug("Cancelling previous timeout task " + (succeeded ? "succeeded" : "failed") + + " for DtxBranch " + _xid); + } + } + + if(_transaction == null) + { + prePrepareTransaction(); + } + else + { + _transaction.removeXid(_xid.getFormat(), _xid.getGlobalId(), _xid.getBranchId()); + } + _transaction.commitTran(); + + for(ServerTransaction.Action action : _postTransactionActions) + { + action.postCommit(); + } + _postTransactionActions.clear(); + } + + public void prePrepareTransaction() throws AMQStoreException + { + _transaction = _store.newTransaction(); + + for(Record enqueue : _enqueueRecords) + { + if(enqueue.isDurable()) + { + _transaction.enqueueMessage(enqueue.getQueue(), enqueue.getMessage()); + } + } + + + for(Record enqueue : _dequeueRecords) + { + if(enqueue.isDurable()) + { + _transaction.dequeueMessage(enqueue.getQueue(), enqueue.getMessage()); + } + } + } + + + public void addPostTransactionAcion(ServerTransaction.Action postTransactionAction) + { + _postTransactionActions.add(postTransactionAction); + } + + + public void dequeue(BaseQueue queue, EnqueableMessage message) + { + _dequeueRecords.add(new Record(queue, message)); + } + + + public void enqueue(BaseQueue queue, EnqueableMessage message) + { + _enqueueRecords.add(new Record(queue, message)); + } + + private static final class Record implements Transaction.Record + { + private final BaseQueue _queue; + private final EnqueableMessage _message; + + public Record(BaseQueue queue, EnqueableMessage message) + { + _queue = queue; + _message = message; + } + + public BaseQueue getQueue() + { + return _queue; + } + + public EnqueableMessage getMessage() + { + return _message; + } + + public boolean isDurable() + { + return _message.isPersistent() && _queue.isDurable(); + } + } + + + public void close() + { + if(_transaction != null) + { + try + { + _state = null; + _transaction.abortTran(); + } + catch(AMQStoreException e) + { + _logger.error("Error while closing DtxBranch " + _xid, e); + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxException.java new file mode 100644 index 0000000000..d18d0cb68b --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxException.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.txn; + +public class DtxException extends Exception +{ + public DtxException() + { + } + + public DtxException(String message) + { + super(message); + } + + public DtxException(String message, Throwable cause) + { + super(message, cause); + } + + public DtxException(Throwable cause) + { + super(cause); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxNotSelectedException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxNotSelectedException.java new file mode 100644 index 0000000000..c1289b1fdd --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxNotSelectedException.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.txn; + +public class DtxNotSelectedException extends DtxException +{ + public DtxNotSelectedException() + { + super("Distribution transactions have not been selected on this session"); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxRegistry.java new file mode 100644 index 0000000000..117beb82ef --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/DtxRegistry.java @@ -0,0 +1,334 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.transport.Xid; + +public class DtxRegistry +{ + private final Map _branches = new HashMap(); + + + private static final class ComparableXid + { + private final Xid _xid; + + private ComparableXid(Xid xid) + { + _xid = xid; + } + + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + if(o == null || getClass() != o.getClass()) + { + return false; + } + + ComparableXid that = (ComparableXid) o; + + return compareBytes(_xid.getBranchId(), that._xid.getBranchId()) + && compareBytes(_xid.getGlobalId(), that._xid.getGlobalId()); + } + + private static boolean compareBytes(byte[] a, byte[] b) + { + if(a.length != b.length) + { + return false; + } + for(int i = 0; i < a.length; i++) + { + if(a[i] != b[i]) + { + return false; + } + } + return true; + } + + + @Override + public int hashCode() + { + int result = 0; + for(int i = 0; i < _xid.getGlobalId().length; i++) + { + result = 31 * result + (int) _xid.getGlobalId()[i]; + } + for(int i = 0; i < _xid.getBranchId().length; i++) + { + result = 31 * result + (int) _xid.getBranchId()[i]; + } + + return result; + } + } + + public synchronized DtxBranch getBranch(Xid xid) + { + return _branches.get(new ComparableXid(xid)); + } + + public synchronized boolean registerBranch(DtxBranch branch) + { + ComparableXid xid = new ComparableXid(branch.getXid()); + if(!_branches.containsKey(xid)) + { + _branches.put(xid, branch); + return true; + } + return false; + } + + synchronized boolean unregisterBranch(DtxBranch branch) + { + return (_branches.remove(new ComparableXid(branch.getXid())) != null); + } + + public synchronized void commit(Xid id, boolean onePhase) + throws IncorrectDtxStateException, UnknownDtxBranchException, AMQStoreException, RollbackOnlyDtxException, TimeoutDtxException + { + DtxBranch branch = getBranch(id); + if(branch != null) + { + synchronized (branch) + { + if(!branch.hasAssociatedActiveSessions()) + { + branch.clearAssociations(); + + if(branch.expired() || branch.getState() == DtxBranch.State.TIMEDOUT) + { + unregisterBranch(branch); + throw new TimeoutDtxException(id); + } + else if(branch.getState() == DtxBranch.State.ROLLBACK_ONLY) + { + throw new RollbackOnlyDtxException(id); + } + else if(onePhase && branch.getState() == DtxBranch.State.PREPARED) + { + throw new IncorrectDtxStateException("Cannot call one-phase commit on a prepared branch", id); + } + else if(!onePhase && branch.getState() != DtxBranch.State.PREPARED) + { + throw new IncorrectDtxStateException("Cannot call two-phase commit on a non-prepared branch", + id); + } + branch.commit(); + branch.setState(DtxBranch.State.FORGOTTEN); + unregisterBranch(branch); + } + else + { + throw new IncorrectDtxStateException("Branch was still associated with a session", id); + } + } + } + else + { + throw new UnknownDtxBranchException(id); + } + } + + public synchronized void prepare(Xid id) + throws UnknownDtxBranchException, + IncorrectDtxStateException, AMQStoreException, RollbackOnlyDtxException, TimeoutDtxException + { + DtxBranch branch = getBranch(id); + if(branch != null) + { + synchronized (branch) + { + if(!branch.hasAssociatedActiveSessions()) + { + branch.clearAssociations(); + + if(branch.expired() || branch.getState() == DtxBranch.State.TIMEDOUT) + { + unregisterBranch(branch); + throw new TimeoutDtxException(id); + } + else if(branch.getState() != DtxBranch.State.ACTIVE + && branch.getState() != DtxBranch.State.ROLLBACK_ONLY) + { + throw new IncorrectDtxStateException("Cannot prepare a transaction in state " + + branch.getState(), id); + } + else + { + branch.prepare(); + branch.setState(DtxBranch.State.PREPARED); + } + } + else + { + throw new IncorrectDtxStateException("Branch still has associated sessions", id); + } + } + } + else + { + throw new UnknownDtxBranchException(id); + } + } + + public synchronized void rollback(Xid id) + throws IncorrectDtxStateException, + UnknownDtxBranchException, + AMQStoreException, TimeoutDtxException + { + + DtxBranch branch = getBranch(id); + if(branch != null) + { + synchronized (branch) + { + if(branch.expired() || branch.getState() == DtxBranch.State.TIMEDOUT) + { + unregisterBranch(branch); + throw new TimeoutDtxException(id); + } + if(!branch.hasAssociatedActiveSessions()) + { + branch.clearAssociations(); + branch.rollback(); + branch.setState(DtxBranch.State.FORGOTTEN); + unregisterBranch(branch); + } + else + { + throw new IncorrectDtxStateException("Branch was still associated with a session", id); + } + } + } + else + { + throw new UnknownDtxBranchException(id); + } + } + + + public void forget(Xid id) throws UnknownDtxBranchException, IncorrectDtxStateException + { + DtxBranch branch = getBranch(id); + if(branch != null) + { + synchronized (branch) + { + if(!branch.hasAssociatedSessions()) + { + if(branch.getState() != DtxBranch.State.HEUR_COM && branch.getState() != DtxBranch.State.HEUR_RB) + { + throw new IncorrectDtxStateException("Branch should not be forgotten - " + + "it is not heuristically complete", id); + } + branch.setState(DtxBranch.State.FORGOTTEN); + unregisterBranch(branch); + } + else + { + throw new IncorrectDtxStateException("Branch was still associated with a session", id); + } + } + } + else + { + throw new UnknownDtxBranchException(id); + } + } + + public long getTimeout(Xid id) throws UnknownDtxBranchException + { + DtxBranch branch = getBranch(id); + if(branch != null) + { + return branch.getTimeout(); + } + else + { + throw new UnknownDtxBranchException(id); + } + } + + public void setTimeout(Xid id, long timeout) throws UnknownDtxBranchException + { + DtxBranch branch = getBranch(id); + if(branch != null) + { + branch.setTimeout(timeout); + } + else + { + throw new UnknownDtxBranchException(id); + } + } + + public synchronized List recover() + { + List inDoubt = new ArrayList(); + for(DtxBranch branch : _branches.values()) + { + if(branch.getState() == DtxBranch.State.PREPARED) + { + inDoubt.add(branch.getXid()); + } + } + return inDoubt; + } + + public synchronized void endAssociations(AMQSessionModel session) + { + for(DtxBranch branch : _branches.values()) + { + if(branch.isAssociated(session)) + { + branch.setState(DtxBranch.State.ROLLBACK_ONLY); + branch.disassociateSession(session); + } + } + + } + + + public synchronized void close() + { + for(DtxBranch branch : _branches.values()) + { + branch.close(); + } + _branches.clear(); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/IncorrectDtxStateException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/IncorrectDtxStateException.java new file mode 100644 index 0000000000..45f094e4b9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/IncorrectDtxStateException.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.txn; + +import org.apache.qpid.transport.Xid; + +public class IncorrectDtxStateException extends DtxException +{ + public IncorrectDtxStateException(String message, Xid id) + { + super(message + " (xid: " + id + ")"); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/JoinAndResumeDtxException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/JoinAndResumeDtxException.java new file mode 100644 index 0000000000..a25e5a4ed6 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/JoinAndResumeDtxException.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.txn; + +import org.apache.qpid.transport.Xid; + +public class JoinAndResumeDtxException extends DtxException +{ + public JoinAndResumeDtxException(Xid id) + { + super("Cannot start a branch with both join and resume set " + id); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/LocalTransaction.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/LocalTransaction.java new file mode 100755 index 0000000000..23265199c7 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/LocalTransaction.java @@ -0,0 +1,483 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.server.store.StoreFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.Transaction; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * A concrete implementation of ServerTransaction where enqueue/dequeue + * operations share a single long-lived transaction. + * + * The caller is responsible for invoking commit() (or rollback()) as necessary. + */ +public class LocalTransaction implements ServerTransaction +{ + protected static final Logger _logger = LoggerFactory.getLogger(LocalTransaction.class); + + private final List _postTransactionActions = new ArrayList(); + + private volatile Transaction _transaction; + private final ActivityTimeAccessor _activityTime; + private final MessageStore _transactionLog; + private volatile long _txnStartTime = 0L; + private volatile long _txnUpdateTime = 0l; + private StoreFuture _asyncTran; + + public LocalTransaction(MessageStore transactionLog) + { + this(transactionLog, new ActivityTimeAccessor() + { + @Override + public long getActivityTime() + { + return System.currentTimeMillis(); + } + }); + } + + public LocalTransaction(MessageStore transactionLog, ActivityTimeAccessor activityTime) + { + _transactionLog = transactionLog; + _activityTime = activityTime; + } + + @Override + public long getTransactionStartTime() + { + return _txnStartTime; + } + + @Override + public long getTransactionUpdateTime() + { + return _txnUpdateTime; + } + + public void addPostTransactionAction(Action postTransactionAction) + { + sync(); + _postTransactionActions.add(postTransactionAction); + } + + public void dequeue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + sync(); + _postTransactionActions.add(postTransactionAction); + initTransactionStartTimeIfNecessaryAndAdvanceUpdateTime(); + + if(message.isPersistent() && queue.isDurable()) + { + try + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeue of message number " + message.getMessageNumber() + " from transaction log. Queue : " + queue.getName()); + } + + beginTranIfNecessary(); + _transaction.dequeueMessage(queue, message); + + } + catch(AMQException e) + { + _logger.error("Error during message dequeues", e); + tidyUpOnError(e); + } + } + } + + public void dequeue(Collection queueEntries, Action postTransactionAction) + { + sync(); + _postTransactionActions.add(postTransactionAction); + initTransactionStartTimeIfNecessaryAndAdvanceUpdateTime(); + + try + { + for(QueueEntry entry : queueEntries) + { + ServerMessage message = entry.getMessage(); + BaseQueue queue = entry.getQueue(); + + if(message.isPersistent() && queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeue of message number " + message.getMessageNumber() + " from transaction log. Queue : " + queue.getName()); + } + + beginTranIfNecessary(); + _transaction.dequeueMessage(queue, message); + } + + } + } + catch(AMQException e) + { + _logger.error("Error during message dequeues", e); + tidyUpOnError(e); + } + } + + private void tidyUpOnError(Exception e) + { + try + { + doRollbackActions(); + } + finally + { + try + { + if (_transaction != null) + { + _transaction.abortTran(); + } + } + catch (Exception abortException) + { + _logger.error("Abort transaction failed while trying to handle previous error", abortException); + } + finally + { + resetDetails(); + } + } + + throw new RuntimeException(e); + } + + private void beginTranIfNecessary() + { + + if(_transaction == null) + { + try + { + _transaction = _transactionLog.newTransaction(); + } + catch (Exception e) + { + tidyUpOnError(e); + } + } + } + + public void enqueue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + sync(); + _postTransactionActions.add(postTransactionAction); + initTransactionStartTimeIfNecessaryAndAdvanceUpdateTime(); + + if(message.isPersistent() && queue.isDurable()) + { + try + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueue of message number " + message.getMessageNumber() + " to transaction log. Queue : " + queue.getName()); + } + + beginTranIfNecessary(); + _transaction.enqueueMessage(queue, message); + } + catch (Exception e) + { + _logger.error("Error during message enqueue", e); + + tidyUpOnError(e); + } + } + } + + public void enqueue(List queues, EnqueableMessage message, Action postTransactionAction) + { + sync(); + _postTransactionActions.add(postTransactionAction); + initTransactionStartTimeIfNecessaryAndAdvanceUpdateTime(); + + if(message.isPersistent()) + { + try + { + for(BaseQueue queue : queues) + { + if(queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueue of message number " + message.getMessageNumber() + " to transaction log. Queue : " + queue.getName() ); + } + + beginTranIfNecessary(); + _transaction.enqueueMessage(queue, message); + } + } + + } + catch (Exception e) + { + _logger.error("Error during message enqueue", e); + + tidyUpOnError(e); + } + } + } + + public void commit() + { + sync(); + commit(null); + } + + public void commit(Runnable immediateAction) + { + sync(); + try + { + if(_transaction != null) + { + _transaction.commitTran(); + } + + if(immediateAction != null) + { + immediateAction.run(); + } + + doPostTransactionActions(); + } + catch (Exception e) + { + _logger.error("Failed to commit transaction", e); + + doRollbackActions(); + throw new RuntimeException("Failed to commit transaction", e); + } + finally + { + resetDetails(); + } + } + + private void doRollbackActions() + { + for(Action action : _postTransactionActions) + { + action.onRollback(); + } + } + + public StoreFuture commitAsync(final Runnable deferred) + { + sync(); + try + { + StoreFuture future = StoreFuture.IMMEDIATE_FUTURE; + if(_transaction != null) + { + future = new StoreFuture() + { + private volatile boolean _completed = false; + private StoreFuture _underlying = _transaction.commitTranAsync(); + + @Override + public boolean isComplete() + { + return _completed || checkUnderlyingCompletion(); + } + + @Override + public void waitForCompletion() + { + if(!_completed) + { + _underlying.waitForCompletion(); + checkUnderlyingCompletion(); + } + } + + private synchronized boolean checkUnderlyingCompletion() + { + if(!_completed && _underlying.isComplete()) + { + completeDeferredWork(); + _completed = true; + } + return _completed; + + } + + private void completeDeferredWork() + { + try + { + doPostTransactionActions(); + deferred.run(); + + } + catch (Exception e) + { + _logger.error("Failed to commit transaction", e); + + doRollbackActions(); + throw new RuntimeException("Failed to commit transaction", e); + } + finally + { + resetDetails(); + } + } + + }; + _asyncTran = future; + } + else + { + try + { + doPostTransactionActions(); + + deferred.run(); + } + finally + { + resetDetails(); + } + } + + return future; + } + catch (Exception e) + { + _logger.error("Failed to commit transaction", e); + try + { + doRollbackActions(); + } + finally + { + resetDetails(); + } + throw new RuntimeException("Failed to commit transaction", e); + } + } + + private void doPostTransactionActions() + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Beginning " + _postTransactionActions.size() + " post transaction actions"); + } + + for(int i = 0; i < _postTransactionActions.size(); i++) + { + _postTransactionActions.get(i).postCommit(); + } + + if(_logger.isDebugEnabled()) + { + _logger.debug("Completed post transaction actions"); + } + } + + public void rollback() + { + sync(); + try + { + if(_transaction != null) + { + _transaction.abortTran(); + } + } + catch (AMQException e) + { + _logger.error("Failed to rollback transaction", e); + throw new RuntimeException("Failed to rollback transaction", e); + } + finally + { + try + { + doRollbackActions(); + } + finally + { + resetDetails(); + } + } + } + + public void sync() + { + if(_asyncTran != null) + { + _asyncTran.waitForCompletion(); + _asyncTran = null; + } + } + + private void initTransactionStartTimeIfNecessaryAndAdvanceUpdateTime() + { + long currentTime = _activityTime.getActivityTime(); + + if (_txnStartTime == 0) + { + _txnStartTime = currentTime; + } + _txnUpdateTime = currentTime; + } + + private void resetDetails() + { + _asyncTran = null; + _transaction = null; + _postTransactionActions.clear(); + _txnStartTime = 0L; + _txnUpdateTime = 0; + } + + public boolean isTransactional() + { + return true; + } + + public interface ActivityTimeAccessor + { + long getActivityTime(); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/NotAssociatedDtxException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/NotAssociatedDtxException.java new file mode 100644 index 0000000000..de070546a7 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/NotAssociatedDtxException.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.txn; + +import org.apache.qpid.transport.Xid; + +public class NotAssociatedDtxException extends DtxException +{ + public NotAssociatedDtxException(Xid id) + { + super("Xid " + id + " not associated with the current session"); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/RollbackOnlyDtxException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/RollbackOnlyDtxException.java new file mode 100644 index 0000000000..6cf12d8631 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/RollbackOnlyDtxException.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.txn; + +import org.apache.qpid.transport.Xid; + +public class RollbackOnlyDtxException extends DtxException +{ + public RollbackOnlyDtxException(Xid id) + { + super("Transaction " + id + " may only be rolled back"); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/ServerTransaction.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/ServerTransaction.java new file mode 100755 index 0000000000..8acac00479 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/ServerTransaction.java @@ -0,0 +1,122 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.txn; + +import java.util.Collection; +import java.util.List; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueEntry; + + +/** + * The ServerTransaction interface allows a set enqueue/dequeue operations to be + * performed against the transaction belonging the underlying TransactionLog object. + * + * Typically all ServerTransaction implementations decide if a message should be enlisted + * into a store transaction by examining the durable property of the queue, and the persistence + * property of the message. + * + * A caller may register a list of post transaction Actions to be + * performed on commit() (or rollback()). + * + */ +public interface ServerTransaction +{ + + + /** + * Represents an action to be performed on transaction commit or rollback + */ + public static interface Action + { + public void postCommit(); + + public void onRollback(); + } + + /** + * Return the time the current transaction started. + * + * @return the time this transaction started or 0 if not in a transaction + */ + long getTransactionStartTime(); + + /** + * Return the time of the last activity on the current transaction. + * + * @return the time of the last activity or 0 if not in a transaction + */ + long getTransactionUpdateTime(); + + /** + * Register an Action for execution after transaction commit or rollback. Actions + * will be executed in the order in which they are registered. + */ + void addPostTransactionAction(Action postTransactionAction); + + /** + * Dequeue a message from a queue registering a post transaction action. + * + * A store operation will result only for a persistent message on a durable queue. + */ + void dequeue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction); + + /** + * Dequeue a message(s) from queue(s) registering a post transaction action. + * + * Store operations will result only for a persistent messages on durable queues. + */ + void dequeue(Collection messages, Action postTransactionAction); + + /** + * Enqueue a message to a queue registering a post transaction action. + * + * A store operation will result only for a persistent message on a durable queue. + */ + void enqueue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction); + + /** + * Enqueue a message(s) to queue(s) registering a post transaction action. + * + * Store operations will result only for a persistent messages on durable queues. + */ + void enqueue(List queues, EnqueableMessage message, Action postTransactionAction); + + /** + * Commit the transaction represented by this object. + * + * If the caller has registered one or more Actions, the postCommit() method on each will + * be executed immediately after the underlying transaction has committed. + */ + void commit(); + + void commit(Runnable immediatePostTransactionAction); + + /** Rollback the transaction represented by this object. + * + * If the caller has registered one or more Actions, the onRollback() method on each will + * be executed immediately after the underlying transaction has rolled-back. + */ + void rollback(); + + boolean isTransactional(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/SuspendAndFailDtxException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/SuspendAndFailDtxException.java new file mode 100644 index 0000000000..228844fd63 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/SuspendAndFailDtxException.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.txn; + +import org.apache.qpid.transport.Xid; + +public class SuspendAndFailDtxException extends DtxException +{ +public SuspendAndFailDtxException(Xid id) +{ + super("Cannot end a branch with both suspend and fail set " + id); +} +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/TimeoutDtxException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/TimeoutDtxException.java new file mode 100644 index 0000000000..50f7708d8a --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/TimeoutDtxException.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.txn; + +import org.apache.qpid.transport.Xid; + +public class TimeoutDtxException extends DtxException +{ + public TimeoutDtxException(Xid id) + { + super("Transaction " + id + " has timed-out and may only be rolled back"); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/UnknownDtxBranchException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/UnknownDtxBranchException.java new file mode 100644 index 0000000000..c23e518365 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/txn/UnknownDtxBranchException.java @@ -0,0 +1,32 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.txn; + +import org.apache.qpid.transport.Xid; + +public class UnknownDtxBranchException extends DtxException +{ + public UnknownDtxBranchException(Xid id) + { + super("Unknown xid " + id); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ByteBufferOutputStream.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ByteBufferOutputStream.java new file mode 100644 index 0000000000..ca9a41bc32 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ByteBufferOutputStream.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.util; + +import java.io.OutputStream; +import java.nio.ByteBuffer; + +public class ByteBufferOutputStream extends OutputStream +{ + private final ByteBuffer _buffer; + + public ByteBufferOutputStream(ByteBuffer buffer) + { + _buffer = buffer; + } + + @Override + public void write(int b) + { + _buffer.put((byte)b); + } + + @Override + public void write(byte[] b, int off, int len) + { + _buffer.put(b, off, len); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapJsonSerializer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapJsonSerializer.java new file mode 100644 index 0000000000..2d9ba38555 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapJsonSerializer.java @@ -0,0 +1,69 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.io.StringWriter; +import java.util.Map; + +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.type.TypeReference; + +public class MapJsonSerializer +{ + private static final TypeReference> MAP_TYPE_REFERENCE = new TypeReference>() + { + }; + + private ObjectMapper _mapper; + + public MapJsonSerializer() + { + _mapper = new ObjectMapper(); + } + + public String serialize(Map attributeMap) + { + StringWriter stringWriter = new StringWriter(); + try + { + _mapper.writeValue(stringWriter, attributeMap); + } + catch (Exception e) + { + throw new RuntimeException("Failure to serialize map:" + attributeMap, e); + } + return stringWriter.toString(); + } + + public Map deserialize(String json) + { + Map attributesMap = null; + try + { + attributesMap = _mapper.readValue(json, MAP_TYPE_REFERENCE); + } + catch (Exception e) + { + throw new RuntimeException("Failure to deserialize json:" + json, e); + } + return attributesMap; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapValueConverter.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapValueConverter.java new file mode 100644 index 0000000000..37e0177b00 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/MapValueConverter.java @@ -0,0 +1,412 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.Array; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class MapValueConverter +{ + + public static String getStringAttribute(String name, Map attributes, String defaultVal) + { + final Object value = attributes.get(name); + return toString(value, defaultVal); + } + + public static String toString(final Object value) + { + return toString(value, null); + } + + public static String toString(final Object value, String defaultVal) + { + if (value == null) + { + return defaultVal; + } + else if (value instanceof String) + { + return (String)value; + } + return String.valueOf(value); + } + + public static String getStringAttribute(String name, Map attributes) + { + assertMandatoryAttribute(name, attributes); + return getStringAttribute(name, attributes, null); + } + + public static void assertMandatoryAttribute(String name, Map attributes) + { + if (!attributes.containsKey(name)) + { + throw new IllegalArgumentException("Value for attribute " + name + " is not found"); + } + } + + public static Map getMapAttribute(String name, Map attributes, Map defaultVal) + { + final Object value = attributes.get(name); + if(value == null) + { + return defaultVal; + } + else if(value instanceof Map) + { + @SuppressWarnings("unchecked") + Map retVal = (Map) value; + return retVal; + } + else + { + throw new IllegalArgumentException("Value for attribute " + name + " is not of required type Map"); + } + } + + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static E getEnumAttribute(Class clazz, String name, Map attributes, E defaultVal) + { + Object obj = attributes.get(name); + if(obj == null) + { + return defaultVal; + } + else if(clazz.isInstance(obj)) + { + return (E) obj; + } + else if(obj instanceof String) + { + return (E) Enum.valueOf(clazz, (String)obj); + } + else + { + throw new IllegalArgumentException("Value for attribute " + name + " is not of required type " + clazz.getSimpleName()); + } + } + + public static > E getEnumAttribute(Class clazz, String name, Map attributes) + { + assertMandatoryAttribute(name, attributes); + return getEnumAttribute(clazz, name, attributes, null); + } + + @SuppressWarnings({ "unchecked" }) + public static > T toEnum(String name, Object rawValue, Class enumType) + { + if (enumType.isInstance(rawValue)) + { + return (T) rawValue; + } + else if (rawValue instanceof String) + { + final String stringValue = (String) rawValue; + + return "null".equals(stringValue) ? null : (T) Enum.valueOf(enumType, stringValue); + } + else if(rawValue == null) + { + return null; + } + else + { + throw new IllegalArgumentException("Value for attribute " + name + " is not of required type " + + enumType.getSimpleName()); + } + } + + public static Boolean getBooleanAttribute(String name, Map attributes, Boolean defaultValue) + { + Object obj = attributes.get(name); + return toBoolean(name, obj, defaultValue); + } + + public static Boolean toBoolean(String name, Object obj) + { + return toBoolean(name, obj, null); + } + + public static Boolean toBoolean(String name, Object obj, Boolean defaultValue) + { + if(obj == null) + { + return defaultValue; + } + else if(obj instanceof Boolean) + { + return (Boolean) obj; + } + else if(obj instanceof String) + { + return Boolean.parseBoolean((String) obj); + } + else + { + throw new IllegalArgumentException("Value for attribute " + name + " is not of required type Boolean"); + } + } + + + public static boolean getBooleanAttribute(String name, Map attributes) + { + assertMandatoryAttribute(name, attributes); + return getBooleanAttribute(name, attributes, null); + } + + public static Integer getIntegerAttribute(String name, Map attributes, Integer defaultValue) + { + Object obj = attributes.get(name); + return toInteger(name, obj, defaultValue); + } + + public static Integer toInteger(String name, Object obj) + { + return toInteger(name, obj, null); + } + + public static Integer toInteger(String name, Object obj, Integer defaultValue) + { + if(obj == null) + { + return defaultValue; + } + else if(obj instanceof Number) + { + return ((Number) obj).intValue(); + } + else if(obj instanceof String) + { + return Integer.valueOf((String) obj); + } + else + { + throw new IllegalArgumentException("Value for attribute " + name + " is not of required type Integer"); + } + } + + public static Integer getIntegerAttribute(String name, Map attributes) + { + assertMandatoryAttribute(name, attributes); + return getIntegerAttribute(name, attributes, null); + } + + public static Long getLongAttribute(String name, Map attributes, Long defaultValue) + { + Object obj = attributes.get(name); + return toLong(name, obj, defaultValue); + } + + public static Long toLong(String name, Object obj) + { + return toLong(name, obj, null); + } + + public static Long toLong(String name, Object obj, Long defaultValue) + { + if(obj == null) + { + return defaultValue; + } + else if(obj instanceof Number) + { + return ((Number) obj).longValue(); + } + else if(obj instanceof String) + { + return Long.valueOf((String) obj); + } + else + { + throw new IllegalArgumentException("Value for attribute " + name + " is not of required type Long"); + } + } + + public static Set getSetAttribute(String name, Map attributes) + { + assertMandatoryAttribute(name, attributes); + return getSetAttribute(name, attributes, Collections.emptySet()); + } + + @SuppressWarnings("unchecked") + public static Set getSetAttribute(String name, Map attributes, Set defaultValue) + { + Object obj = attributes.get(name); + if(obj == null) + { + return defaultValue; + } + else if(obj instanceof Set) + { + return (Set) obj; + } + else + { + throw new IllegalArgumentException("Value for attribute " + name + " is not of required type Set"); + } + } + + public static > Set getEnumSetAttribute(String name, Map attributes, Class clazz) + { + Object obj = attributes.get(name); + if (obj == null) + { + return null; + } + else + { + return toSet(obj, clazz, name); + } + } + + public static Map convert(Map configurationAttributes, Map attributeTypes) + { + return convert(configurationAttributes, attributeTypes, true); + } + + public static Map convert(Map configurationAttributes, + Map attributeTypes, + boolean exclusive) + { + Map attributes = new HashMap(); + for (Map.Entry attribute : configurationAttributes.entrySet()) + { + String attributeName = attribute.getKey(); + Object rawValue = attribute.getValue(); + + if (attributeTypes.containsKey(attributeName)) + { + Type typeObject = attributeTypes.get(attributeName); + + Object value = null; + if (typeObject instanceof Class) + { + Class classObject = (Class)typeObject; + value = convert(rawValue, classObject, attributeName); + } + else if (typeObject instanceof ParameterizedType) + { + ParameterizedType parameterizedType= (ParameterizedType)typeObject; + Type type = parameterizedType.getRawType(); + if (type == Set.class) + { + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments.length != 1) + { + throw new IllegalArgumentException("Set type argument is not specified"); + } + Class classObject = (Class)actualTypeArguments[0]; + value = toSet(rawValue, classObject, attributeName); + } + else + { + throw new IllegalArgumentException("Conversion into " + parameterizedType + " is not yet supported"); + } + } + else + { + throw new IllegalArgumentException("Conversion into " + typeObject + " is not yet supported"); + } + attributes.put(attributeName, value); + } + else if(!exclusive) + { + attributes.put(attributeName, rawValue); + } + } + + return attributes; + } + + public static Set toSet(Object rawValue, Class setItemClass, String attributeName) + { + if (rawValue == null) + { + return null; + } + HashSet set = new HashSet(); + if (rawValue instanceof Iterable) + { + Iterable iterable = (Iterable)rawValue; + for (Object object : iterable) + { + T converted = convert(object, setItemClass, attributeName); + set.add(converted); + } + } + else if (rawValue.getClass().isArray()) + { + int length = Array.getLength(rawValue); + for (int i = 0; i < length; i ++) + { + Object arrayElement = Array.get(rawValue, i); + T converted = convert(arrayElement, setItemClass, attributeName); + set.add(converted); + } + } + else + { + throw new IllegalArgumentException("Cannot convert '" + rawValue.getClass() + "' into Set<" + setItemClass.getSimpleName() + "> for attribute " + attributeName); + } + return set; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static T convert(Object rawValue, Class classObject, String attributeName) + { + Object value; + if (classObject == Long.class || classObject == long.class) + { + value = toLong(attributeName, rawValue); + } + else if (classObject == Integer.class || classObject == int.class) + { + value = toInteger(attributeName, rawValue); + } + else if (classObject == Boolean.class || classObject == boolean.class) + { + value = toBoolean(attributeName, rawValue); + } + else if (classObject == String.class) + { + value = toString(rawValue); + } + else if (Enum.class.isAssignableFrom(classObject)) + { + value = toEnum(attributeName, rawValue, (Class) classObject); + } + else + { + throw new IllegalArgumentException("Cannot convert '" + rawValue + "' of type '" + rawValue.getClass() + + "' into type " + classObject + " for attribute " + attributeName); + } + return (T) value; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ParameterizedTypeImpl.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ParameterizedTypeImpl.java new file mode 100644 index 0000000000..29bc81caab --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ParameterizedTypeImpl.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.util; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +public class ParameterizedTypeImpl implements ParameterizedType +{ + private Class _rawType; + private Type[] _typeArguments; + + public ParameterizedTypeImpl(Class rawType, Class... typeArguments) + { + _rawType = rawType; + _typeArguments = typeArguments; + } + @Override + public Type[] getActualTypeArguments() + { + return _typeArguments; + } + + @Override + public Type getRawType() + { + return _rawType; + } + + @Override + public Type getOwnerType() + { + return _rawType.getDeclaringClass(); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(_rawType.getName()); + if (_typeArguments != null) + { + sb.append("<"); + for (int i = 0; i < _typeArguments.length; i++) + { + sb.append(_typeArguments[i].getClass().getName()); + if (i < _typeArguments.length - 1) + { + sb.append(","); + } + } + sb.append(">"); + } + return sb.toString(); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ResourceBundleLoader.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ResourceBundleLoader.java new file mode 100644 index 0000000000..a0ed4e27f4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/ResourceBundleLoader.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.util; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +public class ResourceBundleLoader +{ + public static Map getResources(String baseName) + { + try + { + ResourceBundle bundle = ResourceBundle.getBundle(baseName); + Map resources = new HashMap(); + Enumeration en = bundle.getKeys(); + while (en.hasMoreElements()) + { + String key = (String) en.nextElement(); + resources.put(key, bundle.getString(key)); + } + return resources; + } + catch(MissingResourceException e) + { + return null; + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/StringUtil.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/StringUtil.java new file mode 100644 index 0000000000..aa17a9493b --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/util/StringUtil.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.util; + +import java.util.Random; + +public class StringUtil +{ + private static final String NUMBERS = "0123456789"; + private static final String LETTERS = "abcdefghijklmnopqrstuvwxwy"; + private static final String OTHERS = "_-"; + private static final char[] CHARACTERS = (NUMBERS + LETTERS + LETTERS.toUpperCase() + OTHERS).toCharArray(); + + private Random _random = new Random(); + + public String randomAlphaNumericString(int maxLength) + { + char[] result = new char[maxLength]; + for (int i = 0; i < maxLength; i++) + { + result[i] = (char) CHARACTERS[_random.nextInt(CHARACTERS.length)]; + } + return new String(result); + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java new file mode 100644 index 0000000000..0cd4f0b6b2 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/AbstractVirtualHost.java @@ -0,0 +1,930 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.server.configuration.ExchangeConfiguration; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.connection.ConnectionRegistry; +import org.apache.qpid.server.connection.IConnectionRegistry; +import org.apache.qpid.server.exchange.DefaultExchangeFactory; +import org.apache.qpid.server.exchange.DefaultExchangeRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.VirtualHostMessages; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.protocol.LinkRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.DefaultQueueRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.stats.StatisticsCounter; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.DurableConfigurationStoreHelper; +import org.apache.qpid.server.store.DurableConfiguredObjectRecoverer; +import org.apache.qpid.server.store.Event; +import org.apache.qpid.server.store.EventListener; +import org.apache.qpid.server.txn.DtxRegistry; +import org.apache.qpid.server.virtualhost.plugins.QueueExistsException; + +public abstract class AbstractVirtualHost implements VirtualHost, IConnectionRegistry.RegistryChangeListener, EventListener +{ + private static final Logger _logger = Logger.getLogger(AbstractVirtualHost.class); + + private static final int HOUSEKEEPING_SHUTDOWN_TIMEOUT = 5; + + private final String _name; + + private final UUID _id; + + private final long _createTime = System.currentTimeMillis(); + + private final ScheduledThreadPoolExecutor _houseKeepingTasks; + + private final VirtualHostRegistry _virtualHostRegistry; + + private final StatisticsGatherer _brokerStatisticsGatherer; + + private final SecurityManager _securityManager; + + private final VirtualHostConfiguration _vhostConfig; + + private final QueueRegistry _queueRegistry; + + private final ExchangeRegistry _exchangeRegistry; + + private final ExchangeFactory _exchangeFactory; + + private final ConnectionRegistry _connectionRegistry; + + private final DtxRegistry _dtxRegistry; + private final AMQQueueFactory _queueFactory; + + private volatile State _state = State.INITIALISING; + + private StatisticsCounter _messagesDelivered, _dataDelivered, _messagesReceived, _dataReceived; + + private final Map _linkRegistry = new HashMap(); + private boolean _blocked; + + public AbstractVirtualHost(VirtualHostRegistry virtualHostRegistry, + StatisticsGatherer brokerStatisticsGatherer, + SecurityManager parentSecurityManager, + VirtualHostConfiguration hostConfig, + org.apache.qpid.server.model.VirtualHost virtualHost) throws Exception + { + if (hostConfig == null) + { + throw new IllegalArgumentException("HostConfig cannot be null"); + } + + if (hostConfig.getName() == null || hostConfig.getName().length() == 0) + { + throw new IllegalArgumentException("Illegal name (" + hostConfig.getName() + ") for virtualhost."); + } + + _virtualHostRegistry = virtualHostRegistry; + _brokerStatisticsGatherer = brokerStatisticsGatherer; + _vhostConfig = hostConfig; + _name = _vhostConfig.getName(); + _dtxRegistry = new DtxRegistry(); + + _id = UUIDGenerator.generateVhostUUID(_name); + + CurrentActor.get().message(VirtualHostMessages.CREATED(_name)); + + _securityManager = new SecurityManager(parentSecurityManager, _vhostConfig.getConfig().getString("security.acl"), _name); + + _connectionRegistry = new ConnectionRegistry(); + _connectionRegistry.addRegistryChangeListener(this); + + _houseKeepingTasks = new ScheduledThreadPoolExecutor(_vhostConfig.getHouseKeepingThreadCount()); + + + _queueRegistry = new DefaultQueueRegistry(this); + + _queueFactory = new AMQQueueFactory(this, _queueRegistry); + + _exchangeFactory = new DefaultExchangeFactory(this); + + _exchangeRegistry = new DefaultExchangeRegistry(this, _queueRegistry); + + initialiseStatistics(); + + initialiseStorage(hostConfig, virtualHost); + + getMessageStore().addEventListener(this, Event.PERSISTENT_MESSAGE_SIZE_OVERFULL); + getMessageStore().addEventListener(this, Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL); + } + + abstract protected void initialiseStorage(VirtualHostConfiguration hostConfig, + org.apache.qpid.server.model.VirtualHost virtualHost) throws Exception; + + public IConnectionRegistry getConnectionRegistry() + { + return _connectionRegistry; + } + + public VirtualHostConfiguration getConfiguration() + { + return _vhostConfig; + } + + public UUID getId() + { + return _id; + } + + public boolean isDurable() + { + return false; + } + + /** + * Initialise a housekeeping task to iterate over queues cleaning expired messages with no consumers + * and checking for idle or open transactions that have exceeded the permitted thresholds. + * + * @param period + */ + private void initialiseHouseKeeping(long period) + { + if (period != 0L) + { + scheduleHouseKeepingTask(period, new VirtualHostHouseKeepingTask()); + } + } + + protected void shutdownHouseKeeping() + { + _houseKeepingTasks.shutdown(); + + try + { + if (!_houseKeepingTasks.awaitTermination(HOUSEKEEPING_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS)) + { + _houseKeepingTasks.shutdownNow(); + } + } + catch (InterruptedException e) + { + _logger.warn("Interrupted during Housekeeping shutdown:", e); + Thread.currentThread().interrupt(); + } + } + + protected void removeHouseKeepingTasks() + { + BlockingQueue taskQueue = _houseKeepingTasks.getQueue(); + for (final Runnable runnable : taskQueue) + { + _houseKeepingTasks.remove(runnable); + } + } + + /** + * Allow other broker components to register a HouseKeepingTask + * + * @param period How often this task should run, in ms. + * @param task The task to run. + */ + public void scheduleHouseKeepingTask(long period, HouseKeepingTask task) + { + _houseKeepingTasks.scheduleAtFixedRate(task, period / 2, period, + TimeUnit.MILLISECONDS); + } + + public ScheduledFuture scheduleTask(long delay, Runnable task) + { + return _houseKeepingTasks.schedule(task, delay, TimeUnit.MILLISECONDS); + } + + public long getHouseKeepingTaskCount() + { + return _houseKeepingTasks.getTaskCount(); + } + + public long getHouseKeepingCompletedTaskCount() + { + return _houseKeepingTasks.getCompletedTaskCount(); + } + + public int getHouseKeepingPoolSize() + { + return _houseKeepingTasks.getCorePoolSize(); + } + + public void setHouseKeepingPoolSize(int newSize) + { + _houseKeepingTasks.setCorePoolSize(newSize); + } + + + public int getHouseKeepingActiveCount() + { + return _houseKeepingTasks.getActiveCount(); + } + + + protected void initialiseModel(VirtualHostConfiguration config) throws ConfigurationException, AMQException + { + _logger.debug("Loading configuration for virtualhost: " + config.getName()); + + _exchangeRegistry.initialise(_exchangeFactory); + + List exchangeNames = config.getExchanges(); + + for (String exchangeName : exchangeNames) + { + configureExchange(config.getExchangeConfiguration(exchangeName)); + } + + String[] queueNames = config.getQueueNames(); + + for (Object queueNameObj : queueNames) + { + String queueName = String.valueOf(queueNameObj); + configureQueue(config.getQueueConfiguration(queueName)); + } + } + + private void configureExchange(ExchangeConfiguration exchangeConfiguration) throws AMQException + { + boolean durable = exchangeConfiguration.getDurable(); + boolean autodelete = exchangeConfiguration.getAutoDelete(); + try + { + Exchange newExchange = createExchange(null, exchangeConfiguration.getName(), exchangeConfiguration.getType(), durable, autodelete, + null); + } + catch(ExchangeExistsException e) + { + _logger.info("Exchange " + exchangeConfiguration.getName() + " already defined. Configuration in XML file ignored"); + } + + } + + private void configureQueue(QueueConfiguration queueConfiguration) throws AMQException, ConfigurationException + { + AMQQueue queue = _queueFactory.createAMQQueueImpl(queueConfiguration); + String queueName = queue.getName(); + + if (queue.isDurable()) + { + DurableConfigurationStoreHelper.createQueue(getDurableConfigurationStore(), queue); + } + + //get the exchange name (returns default exchange name if none was specified) + String exchangeName = queueConfiguration.getExchange(); + + Exchange exchange = _exchangeRegistry.getExchange(exchangeName); + if (exchange == null) + { + throw new ConfigurationException("Attempt to bind queue '" + queueName + "' to unknown exchange:" + exchangeName); + } + + Exchange defaultExchange = _exchangeRegistry.getDefaultExchange(); + + //get routing keys in configuration (returns empty list if none are defined) + List routingKeys = queueConfiguration.getRoutingKeys(); + + for (Object routingKeyNameObj : routingKeys) + { + String routingKey = String.valueOf(routingKeyNameObj); + + if (exchange.equals(defaultExchange)) + { + if(!queueName.equals(routingKey)) + { + throw new ConfigurationException("Illegal attempt to bind queue '" + queueName + + "' to the default exchange with a key other than the queue name: " + routingKey); + } + } + else + { + configureBinding(queue, exchange, routingKey, (Map) queueConfiguration.getBindingArguments(routingKey)); + } + } + + if (!exchange.equals(defaultExchange) && !routingKeys.contains(queueName)) + { + //bind the queue to the named exchange using its name + configureBinding(queue, exchange, queueName, null); + } + + } + + private void configureBinding(AMQQueue queue, Exchange exchange, String routingKey, Map arguments) throws AMQException + { + if (_logger.isInfoEnabled()) + { + _logger.info("Binding queue:" + queue + " with routing key '" + routingKey + "' to exchange:" + exchange.getName()); + } + exchange.addBinding(routingKey, queue, arguments); + } + + public String getName() + { + return _name; + } + + public long getCreateTime() + { + return _createTime; + } + + public QueueRegistry getQueueRegistry() + { + return _queueRegistry; + } + + protected ExchangeRegistry getExchangeRegistry() + { + return _exchangeRegistry; + } + + protected ExchangeFactory getExchangeFactory() + { + return _exchangeFactory; + } + + @Override + public void addVirtualHostListener(final VirtualHostListener listener) + { + _exchangeRegistry.addRegistryChangeListener(new ExchangeRegistry.RegistryChangeListener() + { + @Override + public void exchangeRegistered(Exchange exchange) + { + listener.exchangeRegistered(exchange); + } + + @Override + public void exchangeUnregistered(Exchange exchange) + { + listener.exchangeUnregistered(exchange); + } + }); + _queueRegistry.addRegistryChangeListener(new QueueRegistry.RegistryChangeListener() + { + @Override + public void queueRegistered(AMQQueue queue) + { + listener.queueRegistered(queue); + } + + @Override + public void queueUnregistered(AMQQueue queue) + { + listener.queueUnregistered(queue); + } + }); + _connectionRegistry.addRegistryChangeListener(new IConnectionRegistry.RegistryChangeListener() + { + @Override + public void connectionRegistered(AMQConnectionModel connection) + { + listener.connectionRegistered(connection); + } + + @Override + public void connectionUnregistered(AMQConnectionModel connection) + { + listener.connectionUnregistered(connection); + } + }); + } + + @Override + public AMQQueue getQueue(String name) + { + return _queueRegistry.getQueue(name); + } + + @Override + public AMQQueue getQueue(UUID id) + { + return _queueRegistry.getQueue(id); + } + + @Override + public Collection getQueues() + { + return _queueRegistry.getQueues(); + } + + @Override + public int removeQueue(AMQQueue queue) throws AMQException + { + synchronized (getQueueRegistry()) + { + int purged = queue.delete(); + + getQueueRegistry().unregisterQueue(queue.getName()); + if (queue.isDurable() && !queue.isAutoDelete()) + { + DurableConfigurationStore store = getDurableConfigurationStore(); + DurableConfigurationStoreHelper.removeQueue(store, queue); + } + return purged; + } + } + + @Override + public AMQQueue createQueue(UUID id, + String queueName, + boolean durable, + String owner, + boolean autoDelete, + boolean exclusive, + boolean deleteOnNoConsumer, + Map arguments) throws AMQException + { + + if (queueName == null) + { + throw new IllegalArgumentException("Queue name must not be null"); + } + + // Access check + if (!getSecurityManager().authoriseCreateQueue(autoDelete, + durable, + exclusive, + null, + null, + queueName, + owner)) + { + String description = "Permission denied: queue-name '" + queueName + "'"; + throw new AMQSecurityException(description); + } + + synchronized (_queueRegistry) + { + if(_queueRegistry.getQueue(queueName) != null) + { + throw new QueueExistsException("Queue with name " + queueName + " already exists", _queueRegistry.getQueue(queueName)); + } + if(id == null) + { + + id = UUIDGenerator.generateExchangeUUID(queueName, getName()); + while(_queueRegistry.getQueue(id) != null) + { + id = UUID.randomUUID(); + } + + } + else if(_queueRegistry.getQueue(id) != null) + { + throw new QueueExistsException("Queue with id " + id + " already exists", _queueRegistry.getQueue(queueName)); + } + return _queueFactory.createQueue(id, queueName, durable, owner, autoDelete, exclusive, deleteOnNoConsumer, + arguments); + } + + } + + @Override + public Exchange getExchange(String name) + { + return _exchangeRegistry.getExchange(name); + } + + @Override + public Exchange getExchange(UUID id) + { + return _exchangeRegistry.getExchange(id); + } + + @Override + public Exchange getDefaultExchange() + { + return _exchangeRegistry.getDefaultExchange(); + } + + @Override + public Collection getExchanges() + { + return Collections.unmodifiableCollection(_exchangeRegistry.getExchanges()); + } + + @Override + public Collection> getExchangeTypes() + { + return _exchangeFactory.getRegisteredTypes(); + } + + @Override + public Exchange createExchange(UUID id, + String name, + String type, + boolean durable, + boolean autoDelete, + String alternateExchangeName) + throws AMQException + { + + if(_exchangeRegistry.isReservedExchangeName(name)) + { + throw new ReservedExchangeNameException(name); + } + synchronized (_exchangeRegistry) + { + Exchange existing; + if((existing = _exchangeRegistry.getExchange(name)) !=null) + { + throw new ExchangeExistsException(name,existing); + } + Exchange alternateExchange; + + if(alternateExchangeName != null) + { + alternateExchange = _exchangeRegistry.getExchange(alternateExchangeName); + if(alternateExchange == null) + { + throw new UnknownExchangeException(alternateExchangeName); + } + } + else + { + alternateExchange = null; + } + + if(id == null) + { + id = UUIDGenerator.generateExchangeUUID(name, getName()); + } + + Exchange exchange = _exchangeFactory.createExchange(id, name, type, durable, autoDelete); + exchange.setAlternateExchange(alternateExchange); + _exchangeRegistry.registerExchange(exchange); + if(durable) + { + DurableConfigurationStoreHelper.createExchange(getDurableConfigurationStore(), exchange); + } + return exchange; + } + } + + @Override + public void removeExchange(Exchange exchange, boolean force) throws AMQException + { + if(exchange.hasReferrers()) + { + throw new ExchangeIsAlternateException(exchange.getName()); + } + + for(ExchangeType type : getExchangeTypes()) + { + if(type.getDefaultExchangeName().equals( exchange.getName() )) + { + throw new RequiredExchangeException(exchange.getName()); + } + } + _exchangeRegistry.unregisterExchange(exchange.getName(), !force); + if (exchange.isDurable() && !exchange.isAutoDelete()) + { + DurableConfigurationStoreHelper.removeExchange(getDurableConfigurationStore(), exchange); + } + + } + + public SecurityManager getSecurityManager() + { + return _securityManager; + } + + public void close() + { + //Stop Connections + _connectionRegistry.close(); + _queueRegistry.stopAllAndUnregisterMBeans(); + _dtxRegistry.close(); + closeStorage(); + shutdownHouseKeeping(); + + // clear exchange objects + _exchangeRegistry.clearAndUnregisterMbeans(); + + _state = State.STOPPED; + + CurrentActor.get().message(VirtualHostMessages.CLOSED()); + } + + protected void closeStorage() + { + //Close MessageStore + if (getMessageStore() != null) + { + //Remove MessageStore Interface should not throw Exception + try + { + getMessageStore().close(); + } + catch (Exception e) + { + _logger.error("Failed to close message store", e); + } + } + if (getDurableConfigurationStore() != null) + { + //Remove MessageStore Interface should not throw Exception + try + { + getDurableConfigurationStore().close(); + } + catch (Exception e) + { + _logger.error("Failed to close message store", e); + } + } + } + + + protected Logger getLogger() + { + return _logger; + } + + + + public VirtualHostRegistry getVirtualHostRegistry() + { + return _virtualHostRegistry; + } + + public void registerMessageDelivered(long messageSize) + { + _messagesDelivered.registerEvent(1L); + _dataDelivered.registerEvent(messageSize); + _brokerStatisticsGatherer.registerMessageDelivered(messageSize); + } + + public void registerMessageReceived(long messageSize, long timestamp) + { + _messagesReceived.registerEvent(1L, timestamp); + _dataReceived.registerEvent(messageSize, timestamp); + _brokerStatisticsGatherer.registerMessageReceived(messageSize, timestamp); + } + + public StatisticsCounter getMessageReceiptStatistics() + { + return _messagesReceived; + } + + public StatisticsCounter getDataReceiptStatistics() + { + return _dataReceived; + } + + public StatisticsCounter getMessageDeliveryStatistics() + { + return _messagesDelivered; + } + + public StatisticsCounter getDataDeliveryStatistics() + { + return _dataDelivered; + } + + public void resetStatistics() + { + _messagesDelivered.reset(); + _dataDelivered.reset(); + _messagesReceived.reset(); + _dataReceived.reset(); + + for (AMQConnectionModel connection : _connectionRegistry.getConnections()) + { + connection.resetStatistics(); + } + } + + public void initialiseStatistics() + { + _messagesDelivered = new StatisticsCounter("messages-delivered-" + getName()); + _dataDelivered = new StatisticsCounter("bytes-delivered-" + getName()); + _messagesReceived = new StatisticsCounter("messages-received-" + getName()); + _dataReceived = new StatisticsCounter("bytes-received-" + getName()); + } + + public synchronized LinkRegistry getLinkRegistry(String remoteContainerId) + { + LinkRegistry linkRegistry = _linkRegistry.get(remoteContainerId); + if(linkRegistry == null) + { + linkRegistry = new LinkRegistry(); + _linkRegistry.put(remoteContainerId, linkRegistry); + } + return linkRegistry; + } + + public DtxRegistry getDtxRegistry() + { + return _dtxRegistry; + } + + public String toString() + { + return _name; + } + + public State getState() + { + return _state; + } + + public void block() + { + synchronized (_connectionRegistry) + { + if(!_blocked) + { + _blocked = true; + for(AMQConnectionModel conn : _connectionRegistry.getConnections()) + { + conn.block(); + } + } + } + } + + + public void unblock() + { + synchronized (_connectionRegistry) + { + if(_blocked) + { + _blocked = false; + for(AMQConnectionModel conn : _connectionRegistry.getConnections()) + { + conn.unblock(); + } + } + } + } + + public void connectionRegistered(final AMQConnectionModel connection) + { + if(_blocked) + { + connection.block(); + } + } + + public void connectionUnregistered(final AMQConnectionModel connection) + { + } + + public void event(final Event event) + { + switch(event) + { + case PERSISTENT_MESSAGE_SIZE_OVERFULL: + block(); + break; + case PERSISTENT_MESSAGE_SIZE_UNDERFULL: + unblock(); + break; + } + } + + protected void setState(State state) + { + _state = state; + } + + protected void attainActivation() + { + State finalState = State.ERRORED; + + try + { + initialiseHouseKeeping(_vhostConfig.getHousekeepingCheckPeriod()); + finalState = State.ACTIVE; + } + finally + { + _state = finalState; + reportIfError(_state); + } + } + + protected void reportIfError(State state) + { + if (state == State.ERRORED) + { + CurrentActor.get().message(VirtualHostMessages.ERRORED()); + } + } + + protected Map getDurableConfigurationRecoverers() + { + DurableConfiguredObjectRecoverer[] recoverers = { + new QueueRecoverer(this, getExchangeRegistry(), _queueFactory), + new ExchangeRecoverer(getExchangeRegistry(), getExchangeFactory()), + new BindingRecoverer(this, getExchangeRegistry()) + }; + + final Map recovererMap= new HashMap(); + for(DurableConfiguredObjectRecoverer recoverer : recoverers) + { + recovererMap.put(recoverer.getType(), recoverer); + } + return recovererMap; + } + + private class VirtualHostHouseKeepingTask extends HouseKeepingTask + { + public VirtualHostHouseKeepingTask() + { + super(AbstractVirtualHost.this); + } + + public void execute() + { + for (AMQQueue q : _queueRegistry.getQueues()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Checking message status for queue: " + + q.getName()); + } + try + { + q.checkMessageStatus(); + } catch (Exception e) + { + _logger.error("Exception in housekeeping for queue: " + q.getName(), e); + //Don't throw exceptions as this will stop the + // house keeping task from running. + } + } + for (AMQConnectionModel connection : getConnectionRegistry().getConnections()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Checking for long running open transactions on connection " + connection); + } + for (AMQSessionModel session : connection.getSessionModels()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Checking for long running open transactions on session " + session); + } + try + { + session.checkTransactionStatus(_vhostConfig.getTransactionTimeoutOpenWarn(), + _vhostConfig.getTransactionTimeoutOpenClose(), + _vhostConfig.getTransactionTimeoutIdleWarn(), + _vhostConfig.getTransactionTimeoutIdleClose()); + } catch (Exception e) + { + _logger.error("Exception in housekeeping for connection: " + connection.toString(), e); + } + } + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/BindingRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/BindingRecoverer.java new file mode 100644 index 0000000000..a321c285b7 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/BindingRecoverer.java @@ -0,0 +1,177 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.store.AbstractDurableConfiguredObjectRecoverer; +import org.apache.qpid.server.store.UnresolvedDependency; +import org.apache.qpid.server.store.UnresolvedObject; + +public class BindingRecoverer extends AbstractDurableConfiguredObjectRecoverer +{ + private static final Logger _logger = Logger.getLogger(BindingRecoverer.class); + + private final ExchangeRegistry _exchangeRegistry; + private final VirtualHost _virtualHost; + + public BindingRecoverer(final VirtualHost virtualHost, + final ExchangeRegistry exchangeRegistry) + { + _exchangeRegistry = exchangeRegistry; + _virtualHost = virtualHost; + } + + @Override + public UnresolvedObject createUnresolvedObject(final UUID id, + final String type, + final Map attributes) + { + return new UnresolvedBinding(id, attributes); + } + + @Override + public String getType() + { + return org.apache.qpid.server.model.Binding.class.getSimpleName(); + } + + private class UnresolvedBinding implements UnresolvedObject + { + private final Map _bindingArgumentsMap; + private final String _bindingName; + private final UUID _queueId; + private final UUID _exchangeId; + private final UUID _bindingId; + + private List _unresolvedDependencies = + new ArrayList(); + + private Exchange _exchange; + private AMQQueue _queue; + + public UnresolvedBinding(final UUID id, + final Map attributeMap) + { + _bindingId = id; + _exchangeId = UUID.fromString(String.valueOf(attributeMap.get(org.apache.qpid.server.model.Binding.EXCHANGE))); + _queueId = UUID.fromString(String.valueOf(attributeMap.get(org.apache.qpid.server.model.Binding.QUEUE))); + _exchange = _exchangeRegistry.getExchange(_exchangeId); + if(_exchange == null) + { + _unresolvedDependencies.add(new ExchangeDependency()); + } + _queue = _virtualHost.getQueue(_queueId); + if(_queue == null) + { + _unresolvedDependencies.add(new QueueDependency()); + } + + _bindingName = (String) attributeMap.get(org.apache.qpid.server.model.Binding.NAME); + _bindingArgumentsMap = (Map) attributeMap.get(org.apache.qpid.server.model.Binding.ARGUMENTS); + } + + @Override + public UnresolvedDependency[] getUnresolvedDependencies() + { + return _unresolvedDependencies.toArray(new UnresolvedDependency[_unresolvedDependencies.size()]); + } + + @Override + public Binding resolve() + { + try + { + if(_exchange.getBinding(_bindingName, _queue, _bindingArgumentsMap) == null) + { + _logger.info("Restoring binding: (Exchange: " + _exchange.getName() + ", Queue: " + _queue.getName() + + ", Routing Key: " + _bindingName + ", Arguments: " + _bindingArgumentsMap + ")"); + + _exchange.restoreBinding(_bindingId, _bindingName, _queue, _bindingArgumentsMap); + } + return _exchange.getBinding(_bindingName, _queue, _bindingArgumentsMap); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + } + + private class QueueDependency implements UnresolvedDependency + { + + @Override + public UUID getId() + { + return _queueId; + } + + @Override + public String getType() + { + return Queue.class.getSimpleName(); + } + + @Override + public void resolve(final AMQQueue dependency) + { + _queue = dependency; + _unresolvedDependencies.remove(this); + } + + } + + private class ExchangeDependency implements UnresolvedDependency + { + + @Override + public UUID getId() + { + return _exchangeId; + } + + @Override + public String getType() + { + return org.apache.qpid.server.model.Exchange.class.getSimpleName(); + } + + @Override + public void resolve(final Exchange dependency) + { + _exchange = dependency; + _unresolvedDependencies.remove(this); + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/DefaultUpgraderProvider.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/DefaultUpgraderProvider.java new file mode 100644 index 0000000000..8d05e719ee --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/DefaultUpgraderProvider.java @@ -0,0 +1,266 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.exchange.FilterSupport; +import org.apache.qpid.server.exchange.TopicExchange; +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Exchange; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.queue.QueueArgumentsConverter; +import org.apache.qpid.server.store.ConfiguredObjectRecord; +import org.apache.qpid.server.store.DurableConfigurationRecoverer; +import org.apache.qpid.server.store.DurableConfigurationStoreUpgrader; +import org.apache.qpid.server.store.NonNullUpgrader; +import org.apache.qpid.server.store.NullUpgrader; +import org.apache.qpid.server.store.UpgraderProvider; + +import static org.apache.qpid.server.model.VirtualHost.CURRENT_CONFIG_VERSION; + +public class DefaultUpgraderProvider implements UpgraderProvider +{ + private final ExchangeRegistry _exchangeRegistry; + private final VirtualHost _virtualHost; + + public DefaultUpgraderProvider(final VirtualHost virtualHost, + final ExchangeRegistry exchangeRegistry) + { + _virtualHost = virtualHost; + _exchangeRegistry = exchangeRegistry; + } + + public DurableConfigurationStoreUpgrader getUpgrader(final int configVersion, DurableConfigurationRecoverer recoverer) + { + DurableConfigurationStoreUpgrader currentUpgrader = null; + switch(configVersion) + { + case 0: + currentUpgrader = addUpgrader(currentUpgrader, new Version0Upgrader()); + case 1: + currentUpgrader = addUpgrader(currentUpgrader, new Version1Upgrader()); + case 2: + currentUpgrader = addUpgrader(currentUpgrader, new Version2Upgrader()); + + case CURRENT_CONFIG_VERSION: + currentUpgrader = addUpgrader(currentUpgrader, new NullUpgrader(recoverer)); + break; + + default: + throw new IllegalStateException("Unknown configuration model version: " + configVersion + + ". Attempting to run an older instance against an upgraded configuration?"); + } + return currentUpgrader; + } + + private DurableConfigurationStoreUpgrader addUpgrader(DurableConfigurationStoreUpgrader currentUpgrader, + final DurableConfigurationStoreUpgrader nextUpgrader) + { + if(currentUpgrader == null) + { + currentUpgrader = nextUpgrader; + } + else + { + currentUpgrader.setNextUpgrader(nextUpgrader); + } + return currentUpgrader; + } + + /* + * Removes filters from queue bindings to exchanges other than topic exchanges. In older versions of the broker + * such bindings would have been ignored, starting from the point at which the config version changed, these + * arguments would actually cause selectors to be enforced, thus changing which messages would reach a queue. + */ + private class Version0Upgrader extends NonNullUpgrader + { + private final Map _records = new HashMap(); + + public Version0Upgrader() + { + } + + @Override + public void configuredObject(final UUID id, final String type, Map attributes) + { + _records.put(id, new ConfiguredObjectRecord(id, type, attributes)); + } + + private void removeSelectorArguments(Map binding) + { + @SuppressWarnings("unchecked") + Map arguments = new LinkedHashMap((Map)binding.get(Binding.ARGUMENTS)); + + FilterSupport.removeFilters(arguments); + binding.put(Binding.ARGUMENTS, arguments); + } + + private boolean isTopicExchange(Map binding) + { + UUID exchangeId = UUID.fromString((String)binding.get(Binding.EXCHANGE)); + + if(_records.containsKey(exchangeId)) + { + return "topic".equals(_records.get(exchangeId) + .getAttributes() + .get(org.apache.qpid.server.model.Exchange.TYPE)); + } + else + { + return _exchangeRegistry.getExchange(exchangeId) != null + && _exchangeRegistry.getExchange(exchangeId).getType() == TopicExchange.TYPE; + } + + } + + private boolean hasSelectorArguments(Map binding) + { + @SuppressWarnings("unchecked") + Map arguments = (Map) binding.get(Binding.ARGUMENTS); + return (arguments != null) && FilterSupport.argumentsContainFilter(arguments); + } + + + + @Override + public void complete() + { + for(Map.Entry entry : _records.entrySet()) + { + ConfiguredObjectRecord record = entry.getValue(); + String type = record.getType(); + Map attributes = record.getAttributes(); + UUID id = record.getId(); + if(type.equals(Binding.class.getName()) && hasSelectorArguments(attributes) && !isTopicExchange(attributes)) + { + attributes = new LinkedHashMap(attributes); + removeSelectorArguments(attributes); + + record = new ConfiguredObjectRecord(id, type, attributes); + getUpdateMap().put(id, record); + entry.setValue(record); + + } + getNextUpgrader().configuredObject(id, type, attributes); + } + + getNextUpgrader().complete(); + } + + } + + /* + * Change the type string from org.apache.qpid.server.model.Foo to Foo (in line with the practice in the broker + * configuration store). Also remove bindings which reference non-existant queues or exchanges. + */ + private class Version1Upgrader extends NonNullUpgrader + { + @Override + public void configuredObject(final UUID id, String type, final Map attributes) + { + type = type.substring(1+type.lastIndexOf('.')); + getUpdateMap().put(id, new ConfiguredObjectRecord(id, type, attributes)); + + } + + @Override + public void complete() + { + for(Map.Entry entry : getUpdateMap().entrySet()) + { + final ConfiguredObjectRecord record = entry.getValue(); + if(isBinding(record.getType()) && (unknownExchange((String) record.getAttributes().get(Binding.EXCHANGE)) + || unknownQueue((String) record.getAttributes().get(Binding.QUEUE)))) + { + entry.setValue(null); + } + else + { + getNextUpgrader().configuredObject(record.getId(), record.getType(), record.getAttributes()); + } + } + getNextUpgrader().complete(); + } + + private boolean unknownExchange(final String exchangeIdString) + { + UUID exchangeId = UUID.fromString(exchangeIdString); + ConfiguredObjectRecord localRecord = getUpdateMap().get(exchangeId); + return !((localRecord != null && localRecord.getType().equals(Exchange.class.getSimpleName())) + || _exchangeRegistry.getExchange(exchangeId) != null); + } + + private boolean unknownQueue(final String queueIdString) + { + UUID queueId = UUID.fromString(queueIdString); + ConfiguredObjectRecord localRecord = getUpdateMap().get(queueId); + return !((localRecord != null && localRecord.getType().equals(Queue.class.getSimpleName())) + || _virtualHost.getQueue(queueId) != null); + } + + private boolean isBinding(final String type) + { + return Binding.class.getSimpleName().equals(type); + } + + + } + + /* + * Convert the storage of queue attributes to remove the separate "ARGUMENT" attribute, and flatten the + * attributes into the map using the model attribute names rather than the wire attribute names + */ + private class Version2Upgrader extends NonNullUpgrader + { + + private static final String ARGUMENTS = "arguments"; + + @Override + public void configuredObject(UUID id, String type, Map attributes) + { + if(Queue.class.getSimpleName().equals(type)) + { + Map newAttributes = new LinkedHashMap(); + if(attributes.get(ARGUMENTS) instanceof Map) + { + newAttributes.putAll(QueueArgumentsConverter.convertWireArgsToModel((Map) attributes + .get(ARGUMENTS))); + } + newAttributes.putAll(attributes); + attributes = newAttributes; + getUpdateMap().put(id, new ConfiguredObjectRecord(id,type,attributes)); + } + + getNextUpgrader().configuredObject(id,type,attributes); + } + + @Override + public void complete() + { + getNextUpgrader().complete(); + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeExistsException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeExistsException.java new file mode 100644 index 0000000000..f055760efe --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeExistsException.java @@ -0,0 +1,39 @@ +package org.apache.qpid.server.virtualhost;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.exchange.Exchange; + +public class ExchangeExistsException extends AMQException +{ + private final Exchange _existing; + + public ExchangeExistsException(String name, Exchange existing) + { + super(name); + _existing = existing; + } + + public Exchange getExistingExchange() + { + return _existing; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeIsAlternateException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeIsAlternateException.java new file mode 100644 index 0000000000..4be64a3b94 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeIsAlternateException.java @@ -0,0 +1,30 @@ +package org.apache.qpid.server.virtualhost;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import org.apache.qpid.AMQException; + +public class ExchangeIsAlternateException extends AMQException +{ + public ExchangeIsAlternateException(String name) + { + super(name); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeRecoverer.java new file mode 100644 index 0000000000..6ad7014c47 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ExchangeRecoverer.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.virtualhost; + +import java.util.Map; +import java.util.UUID; +import org.apache.qpid.AMQException; +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.model.LifetimePolicy; +import org.apache.qpid.server.store.AbstractDurableConfiguredObjectRecoverer; +import org.apache.qpid.server.store.UnresolvedDependency; +import org.apache.qpid.server.store.UnresolvedObject; + +public class ExchangeRecoverer extends AbstractDurableConfiguredObjectRecoverer +{ + private final ExchangeRegistry _exchangeRegistry; + private final ExchangeFactory _exchangeFactory; + + public ExchangeRecoverer(final ExchangeRegistry exchangeRegistry, final ExchangeFactory exchangeFactory) + { + _exchangeRegistry = exchangeRegistry; + _exchangeFactory = exchangeFactory; + } + + @Override + public String getType() + { + return org.apache.qpid.server.model.Exchange.class.getSimpleName(); + } + + @Override + public UnresolvedObject createUnresolvedObject(final UUID id, + final String type, + final Map attributes) + { + return new UnresolvedExchange(id, attributes); + } + + private class UnresolvedExchange implements UnresolvedObject + { + private Exchange _exchange; + + public UnresolvedExchange(final UUID id, + final Map attributeMap) + { + String exchangeName = (String) attributeMap.get(org.apache.qpid.server.model.Exchange.NAME); + String exchangeType = (String) attributeMap.get(org.apache.qpid.server.model.Exchange.TYPE); + String lifeTimePolicy = (String) attributeMap.get(org.apache.qpid.server.model.Exchange.LIFETIME_POLICY); + boolean autoDelete = lifeTimePolicy == null + || LifetimePolicy.valueOf(lifeTimePolicy) == LifetimePolicy.AUTO_DELETE; + try + { + _exchange = _exchangeRegistry.getExchange(id); + if(_exchange == null) + { + _exchange = _exchangeRegistry.getExchange(exchangeName); + } + if (_exchange == null) + { + _exchange = _exchangeFactory.restoreExchange(id, exchangeName, exchangeType, autoDelete); + _exchangeRegistry.registerExchange(_exchange); + } + } + catch (AMQException e) + { + throw new RuntimeException("Error recovering exchange uuid " + id + " name " + exchangeName, e); + } + } + + @Override + public UnresolvedDependency[] getUnresolvedDependencies() + { + return new UnresolvedDependency[0]; + } + + @Override + public Exchange resolve() + { + return _exchange; + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/HouseKeepingTask.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/HouseKeepingTask.java new file mode 100644 index 0000000000..1b0e50fd34 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/HouseKeepingTask.java @@ -0,0 +1,84 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.virtualhost; + +import org.apache.log4j.Logger; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.actors.AbstractActor; +import org.apache.qpid.server.logging.actors.CurrentActor; + +public abstract class HouseKeepingTask implements Runnable +{ + private Logger _logger = Logger.getLogger(this.getClass()); + + private VirtualHost _virtualHost; + + private String _name; + + private RootMessageLogger _rootLogger; + public HouseKeepingTask(VirtualHost vhost) + { + _virtualHost = vhost; + _name = _virtualHost.getName() + ":" + this.getClass().getSimpleName(); + _rootLogger = CurrentActor.get().getRootMessageLogger(); + } + + final public void run() + { + String originalThreadName = Thread.currentThread().getName(); + Thread.currentThread().setName(_name); + + CurrentActor.set(new AbstractActor(_rootLogger) + { + @Override + public String getLogMessage() + { + return _name; + } + }); + + try + { + execute(); + } + catch (Exception e) + { + _logger.warn(this.getClass().getSimpleName() + " throw exception: " + e, e); + } + finally + { + CurrentActor.remove(); + + // eagerly revert the thread name to make thread dumps more meaningful if captured after task has finished + Thread.currentThread().setName(originalThreadName); + } + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + /** Execute the plugin. */ + public abstract void execute(); + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java new file mode 100644 index 0000000000..cb7f213f06 --- /dev/null +++ b/qpid/java/broker-core/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 org.apache.qpid.management.common.mbeans.annotations.MBeanAttribute; + +import java.io.IOException; + +/** + * The management interface exposed to allow management of a virtualHost + */ +public interface ManagedVirtualHost +{ + static final String TYPE = "VirtualHost"; + static final int VERSION = 1; + + /** + * Returns the name of the managed virtualHost. + * @return the name of the exchange. + * @throws java.io.IOException + */ + @MBeanAttribute(name="Name", description= TYPE + " Name") + String getName() throws IOException; + + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/QueueRecoverer.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/QueueRecoverer.java new file mode 100644 index 0000000000..02d628da68 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/QueueRecoverer.java @@ -0,0 +1,157 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.virtualhost; + +import java.util.LinkedHashMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueFactory; +import org.apache.qpid.server.store.AbstractDurableConfiguredObjectRecoverer; +import org.apache.qpid.server.store.UnresolvedDependency; +import org.apache.qpid.server.store.UnresolvedObject; + +public class QueueRecoverer extends AbstractDurableConfiguredObjectRecoverer +{ + private static final Logger _logger = Logger.getLogger(QueueRecoverer.class); + private final VirtualHost _virtualHost; + private final ExchangeRegistry _exchangeRegistry; + private final QueueFactory _queueFactory; + + public QueueRecoverer(final VirtualHost virtualHost, + final ExchangeRegistry exchangeRegistry, + final QueueFactory queueFactory) + { + _virtualHost = virtualHost; + _exchangeRegistry = exchangeRegistry; + _queueFactory = queueFactory; + } + + @Override + public String getType() + { + return Queue.class.getSimpleName(); + } + + @Override + public UnresolvedObject createUnresolvedObject(final UUID id, + final String type, + final Map attributes) + { + return new UnresolvedQueue(id, type, attributes); + } + + private class UnresolvedQueue implements UnresolvedObject + { + private final Map _attributes; + private final UUID _alternateExchangeId; + private final UUID _id; + private AMQQueue _queue; + private List _dependencies = new ArrayList(); + private Exchange _alternateExchange; + + public UnresolvedQueue(final UUID id, + final String type, + final Map attributes) + { + _attributes = attributes; + _alternateExchangeId = _attributes.get(Queue.ALTERNATE_EXCHANGE) == null ? null : UUID.fromString((String) _attributes + .get(Queue.ALTERNATE_EXCHANGE)); + _id = id; + if (_alternateExchangeId != null) + { + _alternateExchange = _exchangeRegistry.getExchange(_alternateExchangeId); + if(_alternateExchange == null) + { + _dependencies.add(new AlternateExchangeDependency()); + } + } + } + + @Override + public UnresolvedDependency[] getUnresolvedDependencies() + { + return _dependencies.toArray(new UnresolvedDependency[_dependencies.size()]); + } + + @Override + public AMQQueue resolve() + { + String queueName = (String) _attributes.get(Queue.NAME); + String owner = (String) _attributes.get(Queue.OWNER); + boolean exclusive = (Boolean) _attributes.get(Queue.EXCLUSIVE); + + Map queueArgumentsMap = new LinkedHashMap(_attributes); + queueArgumentsMap.remove(Queue.NAME); + queueArgumentsMap.remove(Queue.OWNER); + queueArgumentsMap.remove(Queue.EXCLUSIVE); + + try + { + _queue = _virtualHost.getQueue(_id); + if(_queue == null) + { + _queue = _virtualHost.getQueue(queueName); + } + + if (_queue == null) + { + _queue = _queueFactory.restoreQueue(_id, queueName, owner, false, exclusive, + false, queueArgumentsMap); + } + } + catch (AMQException e) + { + throw new RuntimeException("Error recovering queue uuid " + _id + " name " + queueName, e); + } + return _queue; + } + + private class AlternateExchangeDependency implements UnresolvedDependency + { + @Override + public UUID getId() + { + return _alternateExchangeId; + } + + @Override + public String getType() + { + return "Exchange"; + } + + @Override + public void resolve(final Object dependency) + { + _alternateExchange = (Exchange) dependency; + _dependencies.remove(this); + } + } + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/RequiredExchangeException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/RequiredExchangeException.java new file mode 100644 index 0000000000..da4c9825b1 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/RequiredExchangeException.java @@ -0,0 +1,30 @@ +package org.apache.qpid.server.virtualhost;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import org.apache.qpid.AMQException; + +public class RequiredExchangeException extends AMQException +{ + public RequiredExchangeException(String name) + { + super(name); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ReservedExchangeNameException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ReservedExchangeNameException.java new file mode 100644 index 0000000000..585f045ad9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/ReservedExchangeNameException.java @@ -0,0 +1,38 @@ +package org.apache.qpid.server.virtualhost;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import org.apache.qpid.AMQException; + +public class ReservedExchangeNameException extends AMQException +{ + private final String _name; + + public ReservedExchangeNameException(String name) + { + super("Attempt to create an exchange using a reserved name or prefix: " + name); + _name = name; + } + + public String getName() + { + return _name; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHost.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHost.java new file mode 100644 index 0000000000..b7e51d88d3 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHost.java @@ -0,0 +1,135 @@ +package org.apache.qpid.server.virtualhost;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.logging.subjects.MessageStoreLogSubject; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.DurableConfigurationRecoverer; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.DurableConfigurationStoreCreator; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.MessageStoreCreator; +import org.apache.qpid.server.store.OperationalLoggingListener; + +public class StandardVirtualHost extends AbstractVirtualHost +{ + private MessageStore _messageStore; + + private DurableConfigurationStore _durableConfigurationStore; + + StandardVirtualHost(VirtualHostRegistry virtualHostRegistry, + StatisticsGatherer brokerStatisticsGatherer, + org.apache.qpid.server.security.SecurityManager parentSecurityManager, + VirtualHostConfiguration hostConfig, VirtualHost virtualHost) throws Exception + { + super(virtualHostRegistry, brokerStatisticsGatherer, parentSecurityManager, hostConfig, virtualHost); + } + + + + private MessageStore initialiseMessageStore(VirtualHostConfiguration hostConfig, VirtualHost virtualHost) throws Exception + { + final Object storeTypeAttr = virtualHost.getAttribute(VirtualHost.STORE_TYPE); + String storeType = storeTypeAttr == null ? null : String.valueOf(storeTypeAttr); + MessageStore messageStore = null; + if (storeType == null) + { + final Class clazz = Class.forName(hostConfig.getMessageStoreClass()); + final Object o = clazz.newInstance(); + + if (!(o instanceof MessageStore)) + { + throw new ClassCastException(clazz + " does not implement " + MessageStore.class); + } + + messageStore = (MessageStore) o; + } + else + { + messageStore = new MessageStoreCreator().createMessageStore(storeType); + } + + final + MessageStoreLogSubject + storeLogSubject = new MessageStoreLogSubject(getName(), messageStore.getClass().getSimpleName()); + OperationalLoggingListener.listen(messageStore, storeLogSubject); + + return messageStore; + } + + private DurableConfigurationStore initialiseConfigurationStore(VirtualHost virtualHost) throws Exception + { + DurableConfigurationStore configurationStore; + final Object storeTypeAttr = virtualHost.getAttribute(VirtualHost.CONFIG_STORE_TYPE); + String storeType = storeTypeAttr == null ? null : String.valueOf(storeTypeAttr); + + if(storeType != null) + { + configurationStore = new DurableConfigurationStoreCreator().createMessageStore(storeType); + } + else if(getMessageStore() instanceof DurableConfigurationStore) + { + configurationStore = (DurableConfigurationStore) getMessageStore(); + } + else + { + throw new ClassCastException(getMessageStore().getClass().getSimpleName() + + " is not an instance of DurableConfigurationStore"); + } + return configurationStore; + } + + + protected void initialiseStorage(VirtualHostConfiguration hostConfig, VirtualHost virtualHost) throws Exception + { + _messageStore = initialiseMessageStore(hostConfig, virtualHost); + + _durableConfigurationStore = initialiseConfigurationStore(virtualHost); + + DurableConfigurationRecoverer configRecoverer = + new DurableConfigurationRecoverer(getName(), getDurableConfigurationRecoverers(), + new DefaultUpgraderProvider(this, getExchangeRegistry())); + _durableConfigurationStore.configureConfigStore(virtualHost, configRecoverer); + + VirtualHostConfigRecoveryHandler recoveryHandler = new VirtualHostConfigRecoveryHandler(this, getExchangeRegistry(), getExchangeFactory()); + _messageStore.configureMessageStore(virtualHost, recoveryHandler, recoveryHandler); + + initialiseModel(hostConfig); + + _messageStore.activate(); + + attainActivation(); + } + + @Override + public MessageStore getMessageStore() + { + return _messageStore; + } + + @Override + public DurableConfigurationStore getDurableConfigurationStore() + { + return _durableConfigurationStore; + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHostFactory.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHostFactory.java new file mode 100644 index 0000000000..08f35c08f9 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/StandardVirtualHostFactory.java @@ -0,0 +1,118 @@ +package org.apache.qpid.server.virtualhost;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.model.adapter.VirtualHostAdapter; +import org.apache.qpid.server.plugin.MessageStoreFactory; +import org.apache.qpid.server.plugin.VirtualHostFactory; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.MessageStoreConstants; +import org.apache.qpid.server.store.MessageStoreCreator; + +public class StandardVirtualHostFactory implements VirtualHostFactory +{ + + public static final String TYPE = "STANDARD"; + + @Override + public String getType() + { + return TYPE; + } + + @Override + public VirtualHost createVirtualHost(VirtualHostRegistry virtualHostRegistry, + StatisticsGatherer brokerStatisticsGatherer, + org.apache.qpid.server.security.SecurityManager parentSecurityManager, + VirtualHostConfiguration hostConfig, + org.apache.qpid.server.model.VirtualHost virtualHost) throws Exception + { + return new StandardVirtualHost(virtualHostRegistry, brokerStatisticsGatherer, parentSecurityManager, hostConfig, virtualHost); + } + + + public static final String STORE_TYPE_ATTRIBUTE = org.apache.qpid.server.model.VirtualHost.STORE_TYPE; + public static final String STORE_PATH_ATTRIBUTE = org.apache.qpid.server.model.VirtualHost.STORE_PATH; + + @Override + public void validateAttributes(Map attributes) + { + + // need store type and path + Object storeType = attributes.get(STORE_TYPE_ATTRIBUTE); + if(!(storeType instanceof String)) + { + + throw new IllegalArgumentException("Attribute '"+ STORE_TYPE_ATTRIBUTE + +"' is required and must be of type String."); + } + final MessageStoreCreator storeCreator = new MessageStoreCreator(); + if(!storeCreator.isValidType((String)storeType)) + { + throw new IllegalArgumentException("Attribute '"+ STORE_TYPE_ATTRIBUTE + +"' has value '"+storeType+"' which is not one of the valid values: " + + storeCreator.getStoreTypes() + "."); + + } + + for(MessageStoreFactory factory : storeCreator.getFactories()) + { + if(factory.getType().equalsIgnoreCase((String)storeType)) + { + factory.validateAttributes(attributes); + } + } + + } + + @Override + public Map createVirtualHostConfiguration(VirtualHostAdapter virtualHostAdapter) + { + Map convertedMap = new LinkedHashMap(); + convertedMap.put("store.type", virtualHostAdapter.getAttribute(org.apache.qpid.server.model.VirtualHost.STORE_TYPE)); + convertedMap.put("store.environment-path", virtualHostAdapter.getAttribute(org.apache.qpid.server.model.VirtualHost.STORE_PATH)); + + return convertedMap; + } + + @Override + public Map convertVirtualHostConfiguration(Configuration configuration) + { + Map convertedMap = new LinkedHashMap(); + Configuration storeConfiguration = configuration.subset("store"); + convertedMap.put(org.apache.qpid.server.model.VirtualHost.STORE_TYPE, storeConfiguration.getString("type")); + convertedMap.put(org.apache.qpid.server.model.VirtualHost.STORE_PATH, storeConfiguration.getString(MessageStoreConstants.ENVIRONMENT_PATH_PROPERTY)); + + convertedMap.put(MessageStoreConstants.OVERFULL_SIZE_ATTRIBUTE, storeConfiguration.getString(MessageStoreConstants.OVERFULL_SIZE_PROPERTY)); + convertedMap.put(MessageStoreConstants.UNDERFULL_SIZE_ATTRIBUTE, storeConfiguration.getString(MessageStoreConstants.UNDERFULL_SIZE_PROPERTY)); + + for(MessageStoreFactory mf : new MessageStoreCreator().getFactories()) + { + convertedMap.putAll(mf.convertStoreConfiguration(storeConfiguration)); + } + + return convertedMap; + + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/State.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/State.java new file mode 100644 index 0000000000..55e2539dcf --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/State.java @@ -0,0 +1,31 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.virtualhost; + +public enum State +{ + INITIALISING, + ACTIVE, + PASSIVE, + STOPPED, + /** Terminal state that signifies the virtual host has experienced an unexpected condition. */ + ERRORED +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/UnknownExchangeException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/UnknownExchangeException.java new file mode 100644 index 0000000000..5704126f62 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/UnknownExchangeException.java @@ -0,0 +1,38 @@ +package org.apache.qpid.server.virtualhost;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import org.apache.qpid.AMQException; + +public class UnknownExchangeException extends AMQException +{ + private final String _exchangeName; + + public UnknownExchangeException(String exchangeName) + { + super(exchangeName); + _exchangeName = exchangeName; + } + + public String getExchangeName() + { + return _exchangeName; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java new file mode 100755 index 0000000000..2ebbedccd4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.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.virtualhost; + +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ScheduledFuture; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.common.Closeable; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.connection.IConnectionRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.protocol.LinkRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.txn.DtxRegistry; + +public interface VirtualHost extends DurableConfigurationStore.Source, Closeable, StatisticsGatherer +{ + IConnectionRegistry getConnectionRegistry(); + + VirtualHostConfiguration getConfiguration(); + + String getName(); + + AMQQueue getQueue(String name); + + AMQQueue getQueue(UUID id); + + Collection getQueues(); + + int removeQueue(AMQQueue queue) throws AMQException; + + AMQQueue createQueue(UUID id, + String queueName, + boolean durable, + String owner, + boolean autoDelete, + boolean exclusive, + boolean deleteOnNoConsumer, + Map arguments) throws AMQException; + + + Exchange createExchange(UUID id, + String exchange, + String type, + boolean durable, + boolean autoDelete, + String alternateExchange) + throws AMQException; + + void removeExchange(Exchange exchange, boolean force) throws AMQException; + + Exchange getExchange(String name); + Exchange getExchange(UUID id); + + + Exchange getDefaultExchange(); + + Collection getExchanges(); + + Collection> getExchangeTypes(); + + DurableConfigurationStore getDurableConfigurationStore(); + + MessageStore getMessageStore(); + + SecurityManager getSecurityManager(); + + void addVirtualHostListener(VirtualHostListener listener); + + void close(); + + UUID getId(); + + void scheduleHouseKeepingTask(long period, HouseKeepingTask task); + + long getHouseKeepingTaskCount(); + + public long getHouseKeepingCompletedTaskCount(); + + int getHouseKeepingPoolSize(); + + void setHouseKeepingPoolSize(int newSize); + + int getHouseKeepingActiveCount(); + + VirtualHostRegistry getVirtualHostRegistry(); + + DtxRegistry getDtxRegistry(); + + LinkRegistry getLinkRegistry(String remoteContainerId); + + ScheduledFuture scheduleTask(long delay, Runnable timeoutTask); + + State getState(); + + public void block(); + + public void unblock(); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostConfigRecoveryHandler.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostConfigRecoveryHandler.java new file mode 100755 index 0000000000..39ca3197b4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostConfigRecoveryHandler.java @@ -0,0 +1,375 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* 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.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.TransactionLogMessages; +import org.apache.qpid.server.logging.subjects.MessageStoreLogSubject; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.MessageStoreRecoveryHandler; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.store.Transaction; +import org.apache.qpid.server.store.TransactionLogRecoveryHandler; +import org.apache.qpid.server.store.TransactionLogResource; +import org.apache.qpid.server.txn.DtxBranch; +import org.apache.qpid.server.txn.DtxRegistry; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.transport.Xid; +import org.apache.qpid.transport.util.Functions; + +public class VirtualHostConfigRecoveryHandler implements + MessageStoreRecoveryHandler, + MessageStoreRecoveryHandler.StoredMessageRecoveryHandler, + TransactionLogRecoveryHandler, + TransactionLogRecoveryHandler.QueueEntryRecoveryHandler, + TransactionLogRecoveryHandler.DtxRecordRecoveryHandler +{ + private static final Logger _logger = Logger.getLogger(VirtualHostConfigRecoveryHandler.class); + + private final VirtualHost _virtualHost; + + private final Map _queueRecoveries = new TreeMap(); + private final Map _recoveredMessages = new HashMap(); + private final Map _unusedMessages = new HashMap(); + + private final ExchangeRegistry _exchangeRegistry; + private final ExchangeFactory _exchangeFactory; + + private MessageStoreLogSubject _logSubject; + private MessageStore _store; + + public VirtualHostConfigRecoveryHandler(VirtualHost virtualHost, + ExchangeRegistry exchangeRegistry, + ExchangeFactory exchangeFactory) + { + _virtualHost = virtualHost; + _exchangeRegistry = exchangeRegistry; + _exchangeFactory = exchangeFactory; + } + + public VirtualHostConfigRecoveryHandler begin(MessageStore store) + { + _logSubject = new MessageStoreLogSubject(_virtualHost.getName(), store.getClass().getSimpleName()); + _store = store; + CurrentActor.get().message(_logSubject, TransactionLogMessages.RECOVERY_START(null, false)); + return this; + } + + public StoredMessageRecoveryHandler begin() + { + return this; + } + + public void message(StoredMessage message) + { + ServerMessage serverMessage = message.getMetaData().getType().createMessage(message); + + _recoveredMessages.put(message.getMessageNumber(), serverMessage); + _unusedMessages.put(message.getMessageNumber(), message); + } + + public void completeMessageRecovery() + { + } + + public void dtxRecord(long format, byte[] globalId, byte[] branchId, + Transaction.Record[] enqueues, + Transaction.Record[] dequeues) + { + Xid id = new Xid(format, globalId, branchId); + DtxRegistry dtxRegistry = _virtualHost.getDtxRegistry(); + DtxBranch branch = dtxRegistry.getBranch(id); + if(branch == null) + { + branch = new DtxBranch(id, _store, _virtualHost); + dtxRegistry.registerBranch(branch); + } + for(Transaction.Record record : enqueues) + { + final AMQQueue queue = _virtualHost.getQueue(record.getQueue().getId()); + if(queue != null) + { + final long messageId = record.getMessage().getMessageNumber(); + final ServerMessage message = _recoveredMessages.get(messageId); + _unusedMessages.remove(messageId); + + if(message != null) + { + final MessageReference ref = message.newReference(); + + + branch.enqueue(queue,message); + + branch.addPostTransactionAcion(new ServerTransaction.Action() + { + + public void postCommit() + { + try + { + + queue.enqueue(message, true, null); + ref.release(); + } + catch (AMQException e) + { + _logger.error("Unable to enqueue message " + message.getMessageNumber() + " into " + + "queue " + queue.getName() + " (from XA transaction)", e); + throw new RuntimeException(e); + } + } + + public void onRollback() + { + ref.release(); + } + }); + } + else + { + StringBuilder xidString = xidAsString(id); + CurrentActor.get().message(_logSubject, + TransactionLogMessages.XA_INCOMPLETE_MESSAGE(xidString.toString(), + Long.toString(messageId))); + + } + + } + else + { + StringBuilder xidString = xidAsString(id); + CurrentActor.get().message(_logSubject, + TransactionLogMessages.XA_INCOMPLETE_QUEUE(xidString.toString(), + record.getQueue().getId().toString())); + + } + } + for(Transaction.Record record : dequeues) + { + final AMQQueue queue = _virtualHost.getQueue(record.getQueue().getId()); + if(queue != null) + { + final long messageId = record.getMessage().getMessageNumber(); + final ServerMessage message = _recoveredMessages.get(messageId); + _unusedMessages.remove(messageId); + + if(message != null) + { + final QueueEntry entry = queue.getMessageOnTheQueue(messageId); + + entry.acquire(); + + branch.dequeue(queue, message); + + branch.addPostTransactionAcion(new ServerTransaction.Action() + { + + public void postCommit() + { + entry.discard(); + } + + public void onRollback() + { + entry.release(); + } + }); + } + else + { + StringBuilder xidString = xidAsString(id); + CurrentActor.get().message(_logSubject, + TransactionLogMessages.XA_INCOMPLETE_MESSAGE(xidString.toString(), + Long.toString(messageId))); + + } + + } + else + { + StringBuilder xidString = xidAsString(id); + CurrentActor.get().message(_logSubject, + TransactionLogMessages.XA_INCOMPLETE_QUEUE(xidString.toString(), + record.getQueue().getId().toString())); + } + + } + + try + { + branch.setState(DtxBranch.State.PREPARED); + branch.prePrepareTransaction(); + } + catch (AMQStoreException e) + { + _logger.error("Unexpected database exception when attempting to prepare a recovered XA transaction " + + xidAsString(id), e); + throw new RuntimeException(e); + } + } + + private static StringBuilder xidAsString(Xid id) + { + return new StringBuilder("(") + .append(id.getFormat()) + .append(',') + .append(Functions.str(id.getGlobalId())) + .append(',') + .append(Functions.str(id.getBranchId())) + .append(')'); + } + + public void completeDtxRecordRecovery() + { + for(StoredMessage m : _unusedMessages.values()) + { + _logger.warn("Message id " + m.getMessageNumber() + " in store, but not in any queue - removing...."); + m.remove(); + } + CurrentActor.get().message(_logSubject, TransactionLogMessages.RECOVERY_COMPLETE(null, false)); + } + + public void complete() + { + } + + public void queueEntry(final UUID queueId, long messageId) + { + AMQQueue queue = _virtualHost.getQueue(queueId); + try + { + if(queue != null) + { + String queueName = queue.getName(); + ServerMessage message = _recoveredMessages.get(messageId); + _unusedMessages.remove(messageId); + + if(message != null) + { + + + if (_logger.isDebugEnabled()) + { + _logger.debug("On recovery, delivering " + message.getMessageNumber() + " to " + queueName); + } + + Integer count = _queueRecoveries.get(queueName); + if (count == null) + { + count = 0; + } + + queue.enqueue(message); + + _queueRecoveries.put(queueName, ++count); + } + else + { + _logger.warn("Message id " + messageId + " referenced in log as enqueued in queue " + queueName + " is unknown, entry will be discarded"); + Transaction txn = _store.newTransaction(); + txn.dequeueMessage(queue, new DummyMessage(messageId)); + txn.commitTranAsync(); + } + } + else + { + _logger.warn("Message id " + messageId + " in log references queue with id " + queueId + " which is not in the configuration, entry will be discarded"); + Transaction txn = _store.newTransaction(); + TransactionLogResource mockQueue = + new TransactionLogResource() + { + @Override + public UUID getId() + { + return queueId; + } + }; + txn.dequeueMessage(mockQueue, new DummyMessage(messageId)); + txn.commitTranAsync(); + } + + } + catch(AMQException e) + { + throw new RuntimeException(e); + } + } + + public DtxRecordRecoveryHandler completeQueueEntryRecovery() + { + + for(Map.Entry entry : _queueRecoveries.entrySet()) + { + CurrentActor.get().message(_logSubject, TransactionLogMessages.RECOVERED(entry.getValue(), entry.getKey())); + + CurrentActor.get().message(_logSubject, TransactionLogMessages.RECOVERY_COMPLETE(entry.getKey(), true)); + } + + + + return this; + } + + private static class DummyMessage implements EnqueableMessage + { + + + private final long _messageId; + + public DummyMessage(long messageId) + { + _messageId = messageId; + } + + public long getMessageNumber() + { + return _messageId; + } + + + public boolean isPersistent() + { + return true; + } + + + public StoredMessage getStoredMessage() + { + return null; + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostFactoryRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostFactoryRegistry.java new file mode 100644 index 0000000000..626615a59f --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostFactoryRegistry.java @@ -0,0 +1,65 @@ +package org.apache.qpid.server.virtualhost;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.plugin.VirtualHostFactory; + +public class VirtualHostFactoryRegistry +{ + private static Map getFactoryMap() + { + Map virtualHostFactories = new HashMap(); + QpidServiceLoader qpidServiceLoader = new QpidServiceLoader(); + Iterable factories = qpidServiceLoader.atLeastOneInstanceOf(VirtualHostFactory.class); + for (VirtualHostFactory virtualHostFactory : factories) + { + String type = virtualHostFactory.getType(); + VirtualHostFactory factory = virtualHostFactories.put(type, virtualHostFactory); + if (factory != null) + { + throw new IllegalStateException("VirtualHostFactory with type name '" + type + + "' is already registered using class '" + factory.getClass().getName() + "', can not register class '" + + virtualHostFactory.getClass().getName() + "'"); + } + } + return virtualHostFactories; + } + + + public static Collection getFactories() + { + return Collections.unmodifiableCollection(getFactoryMap().values()); + } + + public static Collection getVirtualHostTypes() + { + return Collections.unmodifiableCollection(getFactoryMap().keySet()); + } + + public static VirtualHostFactory getFactory(String type) + { + return getFactoryMap().get(type); + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostListener.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostListener.java new file mode 100644 index 0000000000..8527435eea --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostListener.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.virtualhost; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.queue.AMQQueue; + +public interface VirtualHostListener +{ + + public void queueRegistered(AMQQueue queue); + + public void queueUnregistered(AMQQueue queue); + + public void connectionRegistered(AMQConnectionModel connection); + + public void connectionUnregistered(AMQConnectionModel connection); + + public void exchangeRegistered(Exchange exchange); + + public void exchangeUnregistered(Exchange exchange); +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java new file mode 100644 index 0000000000..483e11942b --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java @@ -0,0 +1,95 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.virtualhost; + +import org.apache.qpid.common.Closeable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +public class VirtualHostRegistry implements Closeable +{ + private final Map _registry = new ConcurrentHashMap(); + private String _defaultVirtualHostName; + + + public VirtualHostRegistry() + { + super(); + } + + public synchronized void registerVirtualHost(VirtualHost host) + { + if(_registry.containsKey(host.getName())) + { + throw new IllegalArgumentException("Virtual Host with name " + host.getName() + " already registered."); + } + _registry.put(host.getName(),host); + } + + public synchronized void unregisterVirtualHost(VirtualHost host) + { + _registry.remove(host.getName()); + } + + public VirtualHost getVirtualHost(String name) + { + if(name == null || name.trim().length() == 0 || "/".equals(name.trim())) + { + name = getDefaultVirtualHostName(); + } + + return _registry.get(name); + } + + public VirtualHost getDefaultVirtualHost() + { + return getVirtualHost(getDefaultVirtualHostName()); + } + + private String getDefaultVirtualHostName() + { + return _defaultVirtualHostName; + } + + public void setDefaultVirtualHostName(String defaultVirtualHostName) + { + _defaultVirtualHostName = defaultVirtualHostName; + } + + + public Collection getVirtualHosts() + { + return new ArrayList(_registry.values()); + } + + public void close() + { + for (VirtualHost virtualHost : getVirtualHosts()) + { + virtualHost.close(); + } + } + +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/plugins/QueueExistsException.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/plugins/QueueExistsException.java new file mode 100644 index 0000000000..54f7d0d172 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/server/virtualhost/plugins/QueueExistsException.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.virtualhost.plugins; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.AMQQueue; + +public class QueueExistsException extends AMQException +{ + private final AMQQueue _existing; + + public QueueExistsException(String name, AMQQueue existing) + { + super(name); + _existing = existing; + } + + public AMQQueue getExistingQueue() + { + return _existing; + } +} diff --git a/qpid/java/broker-core/src/main/java/org/apache/qpid/tools/security/Passwd.java b/qpid/java/broker-core/src/main/java/org/apache/qpid/tools/security/Passwd.java new file mode 100644 index 0000000000..cd833c89c4 --- /dev/null +++ b/qpid/java/broker-core/src/main/java/org/apache/qpid/tools/security/Passwd.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.tools.security; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.apache.commons.codec.binary.Base64; + +/** + * Utility to generate user:encodedPassword string for use in md5passwd + */ +public class Passwd +{ + public static void main(String args[]) throws NoSuchAlgorithmException, DigestException, IOException + { + if (args.length != 2) + { + System.out.println("Passwd "); + System.exit(0); + } + + Passwd passwd = new Passwd(); + String output = passwd.getOutput(args[0], args[1]); + System.out.println(output); + } + + public String getOutput(String userName, String password) throws UnsupportedEncodingException, NoSuchAlgorithmException + { + byte[] data = password.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); + + String encodedStr = new String(encoded, Charset.forName("utf-8")); + return userName + ":" + encodedStr; + } +} diff --git a/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.AuthenticationManagerFactory b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.AuthenticationManagerFactory new file mode 100644 index 0000000000..8ff67030ef --- /dev/null +++ b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.AuthenticationManagerFactory @@ -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. +# +org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManagerFactory +org.apache.qpid.server.security.auth.manager.Base64MD5PasswordFileAuthenticationManagerFactory +org.apache.qpid.server.security.auth.manager.ExternalAuthenticationManagerFactory +org.apache.qpid.server.security.auth.manager.KerberosAuthenticationManagerFactory +org.apache.qpid.server.security.auth.manager.PlainPasswordFileAuthenticationManagerFactory +org.apache.qpid.server.security.auth.manager.SimpleLDAPAuthenticationManagerFactory diff --git a/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ConfigurationStoreFactory b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ConfigurationStoreFactory new file mode 100644 index 0000000000..cd314abcae --- /dev/null +++ b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ConfigurationStoreFactory @@ -0,0 +1,20 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +org.apache.qpid.server.configuration.store.factory.JsonConfigurationStoreFactory +org.apache.qpid.server.configuration.store.factory.MemoryConfigurationStoreFactory diff --git a/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.DurableConfigurationStoreFactory b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.DurableConfigurationStoreFactory new file mode 100644 index 0000000000..d183d91f18 --- /dev/null +++ b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.DurableConfigurationStoreFactory @@ -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. +# +org.apache.qpid.server.store.JsonFileConfigStoreFactory diff --git a/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ExchangeType b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ExchangeType new file mode 100644 index 0000000000..4ad646b7a0 --- /dev/null +++ b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.ExchangeType @@ -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. +# +org.apache.qpid.server.exchange.DirectExchangeType +org.apache.qpid.server.exchange.TopicExchangeType +org.apache.qpid.server.exchange.FanoutExchangeType +org.apache.qpid.server.exchange.HeadersExchangeType diff --git a/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.GroupManagerFactory b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.GroupManagerFactory new file mode 100644 index 0000000000..6bfb55ff18 --- /dev/null +++ b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.GroupManagerFactory @@ -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. +# +org.apache.qpid.server.security.group.FileGroupManagerFactory diff --git a/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.JDBCConnectionProviderFactory b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.JDBCConnectionProviderFactory new file mode 100644 index 0000000000..e0ae6e97cc --- /dev/null +++ b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.JDBCConnectionProviderFactory @@ -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. +# +org.apache.qpid.server.store.jdbc.DefaultConnectionProviderFactory diff --git a/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.PreferencesProviderFactory b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.PreferencesProviderFactory new file mode 100644 index 0000000000..9fe8379120 --- /dev/null +++ b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.PreferencesProviderFactory @@ -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. +# +org.apache.qpid.server.model.adapter.FileSystemPreferencesProviderFactory \ No newline at end of file diff --git a/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.VirtualHostFactory b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.VirtualHostFactory new file mode 100644 index 0000000000..81217884e4 --- /dev/null +++ b/qpid/java/broker-core/src/main/resources/META-INF/services/org.apache.qpid.server.plugin.VirtualHostFactory @@ -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. +# +org.apache.qpid.server.virtualhost.StandardVirtualHostFactory diff --git a/qpid/java/broker-core/src/main/resources/initial-config.json b/qpid/java/broker-core/src/main/resources/initial-config.json new file mode 100644 index 0000000000..a203190c4b --- /dev/null +++ b/qpid/java/broker-core/src/main/resources/initial-config.json @@ -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. + * + */ +{ + "name": "Broker", + "storeVersion": 1, + "modelVersion": "1.1", + "defaultVirtualHost" : "default", + "authenticationproviders" : [ { + "name" : "passwordFile", + "type" : "PlainPasswordFile", + "path" : "${qpid.home_dir}/etc/passwd", + "preferencesproviders" : [{ + "name": "fileSystemPreferences", + "type": "FileSystemPreferences", + "path" : "${qpid.work_dir}/preferences/passwordFile" + }] + } ], + "ports" : [ { + "name" : "AMQP", + "port" : "${qpid.amqp_port}", + "authenticationProvider" : "passwordFile" + }, { + "name" : "HTTP", + "port" : "${qpid.http_port}", + "authenticationProvider" : "passwordFile", + "protocols" : [ "HTTP" ] + }, { + "name" : "RMI_REGISTRY", + "port" : "${qpid.rmi_port}", + "protocols" : [ "RMI" ] + }, { + "name" : "JMX_CONNECTOR", + "port" : "${qpid.jmx_port}", + "authenticationProvider" : "passwordFile", + "protocols" : [ "JMX_RMI" ] + }], + "virtualhosts" : [ { + "name" : "default", + "type" : "STANDARD", + "storeType" : "DERBY", + "storePath" : "${qpid.work_dir}/derbystore/default" + } ], + "plugins" : [ { + "pluginType" : "MANAGEMENT-HTTP", + "name" : "httpManagement" + }, { + "pluginType" : "MANAGEMENT-JMX", + "name" : "jmxManagement" + } ] +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/BrokerOptionsTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/BrokerOptionsTest.java new file mode 100644 index 0000000000..c22fcf4a14 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/BrokerOptionsTest.java @@ -0,0 +1,329 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server; + +import java.io.File; +import java.util.Map; + +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.test.utils.QpidTestCase; + +public class BrokerOptionsTest extends QpidTestCase +{ + private BrokerOptions _options; + + protected void setUp() throws Exception + { + super.setUp(); + _options = new BrokerOptions(); + } + + public void testDefaultConfigurationStoreType() + { + assertEquals("json", _options.getConfigurationStoreType()); + } + + public void testOverriddenConfigurationStoreType() + { + _options.setConfigurationStoreType("dby"); + assertEquals("dby", _options.getConfigurationStoreType()); + } + + public void testDefaultConfigurationStoreLocationWithQpidWork() + { + String qpidWork = "/test/value"; + setTestSystemProperty("QPID_WORK", qpidWork); + + String expectedPath = new File(qpidWork, BrokerOptions.DEFAULT_CONFIG_NAME_PREFIX + "." + BrokerOptions.DEFAULT_STORE_TYPE).getAbsolutePath(); + assertEquals (expectedPath, _options.getConfigurationStoreLocation()); + } + + public void testDefaultConfigurationStoreLocationWithoutQpidWork() + { + setTestSystemProperty("QPID_WORK", null); + String userDir = System.getProperty("user.dir"); + + String expectedPath = new File(userDir, "work/" + BrokerOptions.DEFAULT_CONFIG_NAME_PREFIX + "." + BrokerOptions.DEFAULT_STORE_TYPE).getAbsolutePath(); + assertEquals (expectedPath, _options.getConfigurationStoreLocation()); + } + + public void testDefaultConfigurationStoreLocationWithQpidWorkAndDifferentStoreType() + { + String qpidWork = "/test/value"; + setTestSystemProperty("QPID_WORK", qpidWork); + + String storeType = "dby"; + _options.setConfigurationStoreType(storeType); + + String expectedPath = new File(qpidWork, BrokerOptions.DEFAULT_CONFIG_NAME_PREFIX + "." + storeType).getAbsolutePath(); + assertEquals (expectedPath, _options.getConfigurationStoreLocation()); + } + + public void testOverriddenConfigurationStoreLocation() + { + final String testConfigFile = "/my/test/store-location.dby"; + _options.setConfigurationStoreLocation(testConfigFile); + assertEquals(testConfigFile, _options.getConfigurationStoreLocation()); + } + + public void testDefaultLogConfigFileWithQpidHome() + { + String qpidHome = "/test/value"; + setTestSystemProperty(BrokerProperties.PROPERTY_QPID_HOME, qpidHome); + + String expectedPath = new File(qpidHome, BrokerOptions.DEFAULT_LOG_CONFIG_FILE).getAbsolutePath(); + + assertEquals(expectedPath, _options.getLogConfigFileLocation()); + } + + public void testDefaultLogConfigFileWithoutQpiddHome() + { + setTestSystemProperty(BrokerProperties.PROPERTY_QPID_HOME, null); + + String expectedPath = new File(BrokerOptions.DEFAULT_LOG_CONFIG_FILE).getAbsolutePath(); + + assertEquals(expectedPath, _options.getLogConfigFileLocation()); + } + + public void testOverriddenLogConfigFile() + { + final String testLogConfigFile = "etc/mytestlog4j.xml"; + _options.setLogConfigFileLocation(testLogConfigFile); + assertEquals(testLogConfigFile, _options.getLogConfigFileLocation()); + } + + public void testDefaultLogWatchFrequency() + { + assertEquals(0L, _options.getLogWatchFrequency()); + } + + public void testOverridenLogWatchFrequency() + { + final int myFreq = 10 * 1000; + + _options.setLogWatchFrequency(myFreq); + assertEquals(myFreq, _options.getLogWatchFrequency()); + } + + public void testDefaultInitialConfigurationLocation() + { + assertEquals(BrokerOptions.DEFAULT_INITIAL_CONFIG_LOCATION, _options.getInitialConfigurationLocation()); + } + + public void testOverriddenInitialConfigurationLocation() + { + final String testConfigFile = "etc/mytestconfig.json"; + _options.setInitialConfigurationLocation(testConfigFile); + assertEquals(testConfigFile, _options.getInitialConfigurationLocation()); + } + + public void testDefaultManagementMode() + { + assertEquals(false, _options.isManagementMode()); + } + + public void testOverriddenDefaultManagementMode() + { + _options.setManagementMode(true); + assertEquals(true, _options.isManagementMode()); + } + + public void testDefaultManagementModeQuiesceVirtualHosts() + { + assertEquals(false, _options.isManagementModeQuiesceVirtualHosts()); + } + + public void testOverriddenDefaultManagementModeQuiesceVirtualHosts() + { + _options.setManagementModeQuiesceVirtualHosts(true); + assertEquals(true, _options.isManagementModeQuiesceVirtualHosts()); + } + + public void testDefaultManagementModeRmiPortOverride() + { + assertEquals(0, _options.getManagementModeRmiPortOverride()); + } + + public void testOverriddenManagementModeRmiPort() + { + _options.setManagementModeRmiPortOverride(5555); + assertEquals(5555, _options.getManagementModeRmiPortOverride()); + } + + public void testDefaultManagementModeJmxPortOverride() + { + assertEquals(0, _options.getManagementModeJmxPortOverride()); + } + + public void testOverriddenManagementModeJmxPort() + { + _options.setManagementModeJmxPortOverride(5555); + assertEquals(5555, _options.getManagementModeJmxPortOverride()); + } + + public void testDefaultManagementModeHttpPortOverride() + { + assertEquals(0, _options.getManagementModeHttpPortOverride()); + } + + public void testOverriddenManagementModeHttpPort() + { + _options.setManagementModeHttpPortOverride(5555); + assertEquals(5555, _options.getManagementModeHttpPortOverride()); + } + + public void testDefaultSkipLoggingConfiguration() + { + assertFalse(_options.isSkipLoggingConfiguration()); + } + + public void testOverriddenSkipLoggingConfiguration() + { + _options.setSkipLoggingConfiguration(true); + assertTrue(_options.isSkipLoggingConfiguration()); + } + + public void testDefaultOverwriteConfigurationStore() + { + assertFalse(_options.isOverwriteConfigurationStore()); + } + + public void testOverriddenOverwriteConfigurationStore() + { + _options.setOverwriteConfigurationStore(true); + assertTrue(_options.isOverwriteConfigurationStore()); + } + + public void testManagementModePassword() + { + _options.setManagementModePassword("test"); + assertEquals("Unexpected management mode password", "test", _options.getManagementModePassword()); + } + + public void testGetDefaultConfigProperties() + { + //Unset QPID_WORK and QPID_HOME for this test. + //See below for specific tests of behaviour depending on their value + setTestSystemProperty("QPID_WORK", null); + setTestSystemProperty("QPID_HOME", null); + + Map props = _options.getConfigProperties(); + + assertEquals("unexpected number of entries", 5, props.keySet().size()); + + assertEquals(BrokerOptions.DEFAULT_AMQP_PORT_NUMBER, props.get(BrokerOptions.QPID_AMQP_PORT)); + assertEquals(BrokerOptions.DEFAULT_HTTP_PORT_NUMBER, props.get(BrokerOptions.QPID_HTTP_PORT)); + assertEquals(BrokerOptions.DEFAULT_RMI_PORT_NUMBER, props.get(BrokerOptions.QPID_RMI_PORT)); + assertEquals(BrokerOptions.DEFAULT_JMX_PORT_NUMBER, props.get(BrokerOptions.QPID_JMX_PORT)); + assertEquals(BrokerOptions.DEFAULT_JMX_PORT_NUMBER, props.get(BrokerOptions.QPID_JMX_PORT)); + assertTrue(props.containsKey(BrokerOptions.QPID_WORK_DIR)); + assertFalse(props.containsKey(BrokerOptions.QPID_HOME_DIR)); + } + + public void testDefaultWorkDirWithQpidWork() + { + String qpidWork = new File(File.separator + "test" + File.separator + "value").getAbsolutePath(); + setTestSystemProperty("QPID_WORK", qpidWork); + + assertEquals (qpidWork, _options.getConfigProperties().get(BrokerOptions.QPID_WORK_DIR)); + } + + public void testDefaultWorkDirWithoutQpidWork() + { + setTestSystemProperty("QPID_WORK", null); + String userDir = System.getProperty("user.dir"); + + String expectedPath = new File(userDir, "work").getAbsolutePath(); + assertEquals (expectedPath, _options.getConfigProperties().get(BrokerOptions.QPID_WORK_DIR)); + } + + public void testOverriddenWorkDir() + { + final String testWorkDir = "/my/test/work/dir"; + _options.setConfigProperty(BrokerOptions.QPID_WORK_DIR, testWorkDir); + assertEquals(testWorkDir, _options.getConfigProperties().get(BrokerOptions.QPID_WORK_DIR)); + } + + public void testDefaultHomeDirWithQpidHome() + { + String qpidHome = new File(File.separator + "test" + File.separator + "value").getAbsolutePath(); + setTestSystemProperty("QPID_HOME", qpidHome); + + assertEquals (qpidHome, _options.getConfigProperties().get(BrokerOptions.QPID_HOME_DIR)); + assertEquals("unexpected number of entries", 6, _options.getConfigProperties().keySet().size()); + } + + public void testDefaultomeDirWithoutQpidHome() + { + setTestSystemProperty("QPID_HOME", null); + + assertNull(_options.getConfigProperties().get(BrokerOptions.QPID_HOME_DIR)); + assertFalse(_options.getConfigProperties().containsKey(BrokerOptions.QPID_HOME_DIR)); + assertEquals("unexpected number of entries", 5, _options.getConfigProperties().keySet().size()); + } + + public void testOverriddenHomeDir() + { + final String testHomeDir = "/my/test/home/dir"; + _options.setConfigProperty(BrokerOptions.QPID_HOME_DIR, testHomeDir); + assertEquals(testHomeDir, _options.getConfigProperties().get(BrokerOptions.QPID_HOME_DIR)); + assertEquals("unexpected number of entries", 6, _options.getConfigProperties().keySet().size()); + } + + public void testSetDefaultConfigProperties() + { + //Unset QPID_WORK and QPID_HOME for this test. + //See above for specific tests of behaviour depending on their value + setTestSystemProperty("QPID_WORK", null); + setTestSystemProperty("QPID_HOME", null); + + String oldPort = BrokerOptions.DEFAULT_AMQP_PORT_NUMBER; + String newPort = "12345"; + + //set a new value for a previously defaulted port number property + _options.setConfigProperty(BrokerOptions.QPID_AMQP_PORT, newPort); + Map props = _options.getConfigProperties(); + assertEquals("unexpected number of entries", 5, props.keySet().size()); + assertEquals(newPort, props.get(BrokerOptions.QPID_AMQP_PORT)); + assertEquals(BrokerOptions.DEFAULT_HTTP_PORT_NUMBER, props.get(BrokerOptions.QPID_HTTP_PORT)); + assertEquals(BrokerOptions.DEFAULT_RMI_PORT_NUMBER, props.get(BrokerOptions.QPID_RMI_PORT)); + assertEquals(BrokerOptions.DEFAULT_JMX_PORT_NUMBER, props.get(BrokerOptions.QPID_JMX_PORT)); + + //clear the value to ensure the default returns + _options.setConfigProperty(BrokerOptions.QPID_AMQP_PORT, null); + props = _options.getConfigProperties(); + assertEquals("unexpected number of entries", 5, props.keySet().size()); + assertEquals(oldPort, props.get(BrokerOptions.QPID_AMQP_PORT)); + assertEquals(BrokerOptions.DEFAULT_HTTP_PORT_NUMBER, props.get(BrokerOptions.QPID_HTTP_PORT)); + assertEquals(BrokerOptions.DEFAULT_RMI_PORT_NUMBER, props.get(BrokerOptions.QPID_RMI_PORT)); + assertEquals(BrokerOptions.DEFAULT_JMX_PORT_NUMBER, props.get(BrokerOptions.QPID_JMX_PORT)); + + //set a user specified property + _options.setConfigProperty("name", "value"); + props = _options.getConfigProperties(); + assertEquals("unexpected number of entries", 6, props.keySet().size()); + assertEquals(oldPort, props.get(BrokerOptions.QPID_AMQP_PORT)); + assertEquals(BrokerOptions.DEFAULT_HTTP_PORT_NUMBER, props.get(BrokerOptions.QPID_HTTP_PORT)); + assertEquals(BrokerOptions.DEFAULT_RMI_PORT_NUMBER, props.get(BrokerOptions.QPID_RMI_PORT)); + assertEquals(BrokerOptions.DEFAULT_JMX_PORT_NUMBER, props.get(BrokerOptions.QPID_JMX_PORT)); + assertEquals("value", props.get("name")); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/MainTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/MainTest.java new file mode 100644 index 0000000000..f3b1749808 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/MainTest.java @@ -0,0 +1,284 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server; + +import java.io.File; +import java.util.Map; + +import org.apache.commons.cli.CommandLine; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.test.utils.QpidTestCase; + +/** + * Test to verify the command line parsing within the Main class, by + * providing it a series of command line arguments and verifying the + * BrokerOptions emerging for use in starting the Broker instance. + */ +public class MainTest extends QpidTestCase +{ + private Exception _startupException; + + public void testNoOptionsSpecified() + { + String qpidWork = "/qpid/work"; + setTestSystemProperty(BrokerProperties.PROPERTY_QPID_WORK, qpidWork); + String qpidHome = "/qpid/home"; + setTestSystemProperty(BrokerProperties.PROPERTY_QPID_HOME, qpidHome); + + String expectedStorePath = new File(qpidWork, BrokerOptions.DEFAULT_CONFIG_NAME_PREFIX + ".json").getAbsolutePath(); + String expectedLogConfigPath = new File(qpidHome, BrokerOptions.DEFAULT_LOG_CONFIG_FILE).getAbsolutePath(); + + BrokerOptions options = startDummyMain(""); + + assertEquals("json", options.getConfigurationStoreType()); + assertEquals(expectedStorePath, options.getConfigurationStoreLocation()); + assertEquals(expectedLogConfigPath, options.getLogConfigFileLocation()); + assertEquals(0, options.getLogWatchFrequency()); + assertEquals(BrokerOptions.DEFAULT_INITIAL_CONFIG_LOCATION, options.getInitialConfigurationLocation()); + assertFalse(options.isOverwriteConfigurationStore()); + assertFalse(options.isManagementMode()); + assertEquals(0, options.getManagementModeJmxPortOverride()); + assertEquals(0, options.getManagementModeRmiPortOverride()); + assertEquals(0, options.getManagementModeHttpPortOverride()); + } + + public void testConfigurationStoreLocation() + { + BrokerOptions options = startDummyMain("-sp abcd/config.xml"); + assertEquals("abcd/config.xml", options.getConfigurationStoreLocation()); + + options = startDummyMain("-store-path abcd/config2.xml"); + assertEquals("abcd/config2.xml", options.getConfigurationStoreLocation()); + } + + public void testConfigurationStoreType() + { + BrokerOptions options = startDummyMain("-st dby"); + assertEquals("dby", options.getConfigurationStoreType()); + + options = startDummyMain("-store-type bdb"); + assertEquals("bdb", options.getConfigurationStoreType()); + } + + public void testOverwriteConfigurationStore() + { + BrokerOptions options = startDummyMain("-os"); + assertTrue(options.isOverwriteConfigurationStore()); + + options = startDummyMain("-overwrite-store"); + assertTrue(options.isOverwriteConfigurationStore()); + } + + public void testLogConfig() + { + BrokerOptions options = startDummyMain("-l wxyz/log4j.xml"); + + assertEquals("wxyz/log4j.xml", options.getLogConfigFileLocation()); + } + + public void testLogWatch() + { + BrokerOptions options = startDummyMain("-w 9"); + + assertEquals(9, options.getLogWatchFrequency()); + } + + public void testVersion() + { + final TestMain main = new TestMain("-v".split("\\s")); + + assertNotNull("Command line not parsed correctly", main.getCommandLine()); + assertTrue("Parsed command line didnt pick up version option", main.getCommandLine().hasOption("v")); + } + + public void testHelp() + { + final TestMain main = new TestMain("-h".split("\\s")); + + assertNotNull("Command line not parsed correctly", main.getCommandLine()); + assertTrue("Parsed command line didnt pick up help option", main.getCommandLine().hasOption("h")); + } + + public void testInitailConfigurationLocation() + { + BrokerOptions options = startDummyMain("-icp abcd/initial-config.json"); + assertEquals("abcd/initial-config.json", options.getInitialConfigurationLocation()); + + options = startDummyMain("-initial-config-path abcd/initial-config.json"); + assertEquals("abcd/initial-config.json", options.getInitialConfigurationLocation()); + } + + public void testManagementMode() + { + BrokerOptions options = startDummyMain("-mm"); + assertTrue(options.isManagementMode()); + + options = startDummyMain("--management-mode"); + assertTrue(options.isManagementMode()); + } + + public void testManagementModeRmiPortOverride() + { + BrokerOptions options = startDummyMain("-mm -mmrmi 7777"); + assertTrue(options.isManagementMode()); + assertEquals(7777, options.getManagementModeRmiPortOverride()); + + options = startDummyMain("-mm --management-mode-rmi-registry-port 7777"); + assertTrue(options.isManagementMode()); + assertEquals(7777, options.getManagementModeRmiPortOverride()); + + options = startDummyMain("-mmrmi 7777"); + assertEquals(0, options.getManagementModeRmiPortOverride()); + } + + public void testManagementModeJmxPortOverride() + { + BrokerOptions options = startDummyMain("-mm -mmjmx 8888"); + assertTrue(options.isManagementMode()); + assertEquals(8888, options.getManagementModeJmxPortOverride()); + + options = startDummyMain("-mm --management-mode-jmx-connector-port 8888"); + assertTrue(options.isManagementMode()); + assertEquals(8888, options.getManagementModeJmxPortOverride()); + + options = startDummyMain("-mmjmx 8888"); + assertEquals(0, options.getManagementModeJmxPortOverride()); + } + + public void testManagementModeHttpPortOverride() + { + BrokerOptions options = startDummyMain("-mm -mmhttp 9999"); + assertTrue(options.isManagementMode()); + assertEquals(9999, options.getManagementModeHttpPortOverride()); + + options = startDummyMain("-mm --management-mode-http-port 9999"); + assertTrue(options.isManagementMode()); + assertEquals(9999, options.getManagementModeHttpPortOverride()); + + options = startDummyMain("-mmhttp 9999"); + assertEquals(0, options.getManagementModeHttpPortOverride()); + } + + public void testManagementModePassword() + { + String password = getTestName(); + BrokerOptions options = startDummyMain("-mm -mmpass " + password); + assertTrue(options.isManagementMode()); + assertEquals(password, options.getManagementModePassword()); + + options = startDummyMain("-mm --management-mode-password " + password); + assertTrue(options.isManagementMode()); + assertEquals(password, options.getManagementModePassword()); + + options = startDummyMain("-mmpass " + password); + assertNotNull(options.getManagementModePassword()); + } + + public void testDefaultManagementModePassword() + { + BrokerOptions options = startDummyMain("-mm"); + assertTrue(options.isManagementMode()); + assertNotNull(options.getManagementModePassword()); + } + + public void testSetConfigProperties() + { + //short name + String newPort = "12345"; + BrokerOptions options = startDummyMain("-prop name=value -prop " + BrokerOptions.QPID_AMQP_PORT + "=" + newPort); + + Map props = options.getConfigProperties(); + + assertEquals(newPort, props.get(BrokerOptions.QPID_AMQP_PORT)); + assertEquals("value", props.get("name")); + + //long name + newPort = "678910"; + options = startDummyMain("--config-property name2=value2 --config-property " + BrokerOptions.QPID_AMQP_PORT + "=" + newPort); + + props = options.getConfigProperties(); + + assertEquals(newPort, props.get(BrokerOptions.QPID_AMQP_PORT)); + assertEquals("value2", props.get("name2")); + } + + public void testSetConfigPropertiesInvalidFormat() + { + //missing equals + startDummyMain("-prop namevalue"); + assertTrue("expected exception did not occur", + _startupException instanceof IllegalArgumentException); + + //no name specified + startDummyMain("-prop =value"); + assertTrue("expected exception did not occur", + _startupException instanceof IllegalArgumentException); + } + + private BrokerOptions startDummyMain(String commandLine) + { + return (new TestMain(commandLine.split("\\s"))).getOptions(); + } + + private class TestMain extends Main + { + private BrokerOptions _options; + + public TestMain(String[] args) + { + super(args); + } + + @Override + protected void execute() + { + try + { + super.execute(); + } + catch(Exception re) + { + MainTest.this._startupException = re; + } + } + + @Override + protected void startBroker(BrokerOptions options) + { + _options = options; + } + + @Override + protected void setExceptionHandler() + { + } + + public BrokerOptions getOptions() + { + return _options; + } + + public CommandLine getCommandLine() + { + return _commandLine; + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/SelectorParserTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/SelectorParserTest.java new file mode 100644 index 0000000000..3e0e217eee --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/SelectorParserTest.java @@ -0,0 +1,140 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server; + +import junit.framework.TestCase; + +import org.apache.qpid.filter.SelectorParsingException; +import org.apache.qpid.filter.selector.ParseException; +import org.apache.qpid.server.filter.JMSSelectorFilter; + +public class SelectorParserTest extends TestCase +{ + public void testSelectorWithHyphen() + { + testPass("Cost = 2 AND \"property-with-hyphen\" = 'wibble'"); + } + + public void testLike() + { + testFail("Cost LIKE 2"); + testPass("Cost LIKE 'Hello'"); + } + + public void testStringQuoted() + { + testPass("string = 'Test'"); + } + + public void testProperty() + { + testPass("prop1 = prop2"); + } + + public void testPropertyNames() + { + testPass("$min= TRUE AND _max= FALSE AND Prop_2 = true AND prop$3 = false"); + } + + public void testProtected() + { + testFail("NULL = 0 "); + testFail("TRUE = 0 "); + testFail("FALSE = 0 "); + testFail("NOT = 0 "); + testFail("AND = 0 "); + testFail("OR = 0 "); + testFail("BETWEEN = 0 "); + testFail("LIKE = 0 "); + testFail("IN = 0 "); + testFail("IS = 0 "); + testFail("ESCAPE = 0 "); + } + + + public void testBoolean() + { + testPass("min= TRUE AND max= FALSE "); + testPass("min= true AND max= false"); + } + + public void testDouble() + { + testPass("positive=31E2 AND negative=-31.4E3"); + testPass("min=" + Double.MIN_VALUE + " AND max=" + Double.MAX_VALUE); + } + + public void testLong() + { + testPass("minLong=" + Long.MIN_VALUE + "L AND maxLong=" + Long.MAX_VALUE + "L"); + } + + public void testInt() + { + testPass("minInt=" + Integer.MIN_VALUE + " AND maxInt=" + Integer.MAX_VALUE); + } + + public void testSigned() + { + testPass("negative=-42 AND positive=+42"); + } + + public void testOctal() + { + testPass("octal=042"); + } + + + private void testPass(String selector) + { + try + { + new JMSSelectorFilter(selector); + } + catch (ParseException e) + { + fail("Selector '" + selector + "' was not parsed :" + e.getMessage()); + } + catch (SelectorParsingException e) + { + fail("Selector '" + selector + "' was not parsed :" + e.getMessage()); + } + } + + private void testFail(String selector) + { + try + { + new JMSSelectorFilter(selector); + fail("Selector '" + selector + "' was parsed "); + } + catch (ParseException e) + { + //normal path + } + catch (SelectorParsingException e) + { + //normal path + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/TransactionTimeoutHelperTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/TransactionTimeoutHelperTest.java new file mode 100644 index 0000000000..96078d766c --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/TransactionTimeoutHelperTest.java @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.qpid.server.logging.messages.ChannelMessages.IDLE_TXN_LOG_HIERARCHY; +import static org.apache.qpid.server.logging.messages.ChannelMessages.OPEN_TXN_LOG_HIERARCHY; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import org.apache.qpid.server.TransactionTimeoutHelper.CloseAction; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.test.utils.QpidTestCase; +import org.hamcrest.Description; +import org.mockito.ArgumentMatcher; + +public class TransactionTimeoutHelperTest extends QpidTestCase +{ + private final LogActor _logActor = mock(LogActor.class); + private final LogSubject _logSubject = mock(LogSubject.class); + private final ServerTransaction _transaction = mock(ServerTransaction.class); + private final CloseAction _closeAction = mock(CloseAction.class); + private TransactionTimeoutHelper _transactionTimeoutHelper; + private long _now; + + public void testNotTransactional() throws Exception + { + when(_transaction.isTransactional()).thenReturn(false); + + _transactionTimeoutHelper.checkIdleOrOpenTimes(_transaction, 5, 10, 5, 10); + + verifyZeroInteractions(_logActor, _closeAction); + } + + public void testOpenTransactionProducesWarningOnly() throws Exception + { + final long sixtyOneSecondsAgo = _now - SECONDS.toMillis(61); + + configureMockTransaction(sixtyOneSecondsAgo, sixtyOneSecondsAgo); + + _transactionTimeoutHelper.checkIdleOrOpenTimes(_transaction, SECONDS.toMillis(30), 0, 0, 0); + + verify(_logActor).message(same(_logSubject), isLogMessage(OPEN_TXN_LOG_HIERARCHY, "CHN-1007 : Open Transaction : 61,\\d{3} ms")); + verifyZeroInteractions(_closeAction); + } + + public void testOpenTransactionProducesTimeoutActionOnly() throws Exception + { + final long sixtyOneSecondsAgo = _now - SECONDS.toMillis(61); + + configureMockTransaction(sixtyOneSecondsAgo, sixtyOneSecondsAgo); + + _transactionTimeoutHelper.checkIdleOrOpenTimes(_transaction, 0, SECONDS.toMillis(30), 0, 0); + + verify(_closeAction).doTimeoutAction("Open transaction timed out"); + verifyZeroInteractions(_logActor); + } + + public void testOpenTransactionProducesWarningAndTimeoutAction() throws Exception + { + final long sixtyOneSecondsAgo = _now - SECONDS.toMillis(61); + + configureMockTransaction(sixtyOneSecondsAgo, sixtyOneSecondsAgo); + + _transactionTimeoutHelper.checkIdleOrOpenTimes(_transaction, SECONDS.toMillis(15), SECONDS.toMillis(30), 0, 0); + + verify(_logActor).message(same(_logSubject), isLogMessage(OPEN_TXN_LOG_HIERARCHY, "CHN-1007 : Open Transaction : 61,\\d{3} ms")); + verify(_closeAction).doTimeoutAction("Open transaction timed out"); + } + + public void testIdleTransactionProducesWarningOnly() throws Exception + { + final long sixtyOneSecondsAgo = _now - SECONDS.toMillis(61); + final long thrityOneSecondsAgo = _now - SECONDS.toMillis(31); + + configureMockTransaction(sixtyOneSecondsAgo, thrityOneSecondsAgo); + + _transactionTimeoutHelper.checkIdleOrOpenTimes(_transaction, 0, 0, SECONDS.toMillis(30), 0); + + verify(_logActor).message(same(_logSubject), isLogMessage(IDLE_TXN_LOG_HIERARCHY, "CHN-1008 : Idle Transaction : 31,\\d{3} ms")); + verifyZeroInteractions(_closeAction); + } + + public void testIdleTransactionProducesTimeoutActionOnly() throws Exception + { + final long sixtyOneSecondsAgo = _now - SECONDS.toMillis(61); + final long thrityOneSecondsAgo = _now - SECONDS.toMillis(31); + + configureMockTransaction(sixtyOneSecondsAgo, thrityOneSecondsAgo); + + _transactionTimeoutHelper.checkIdleOrOpenTimes(_transaction, 0, 0, 0, SECONDS.toMillis(30)); + + verify(_closeAction).doTimeoutAction("Idle transaction timed out"); + verifyZeroInteractions(_logActor); + } + + public void testIdleTransactionProducesWarningAndTimeoutAction() throws Exception + { + final long sixtyOneSecondsAgo = _now - SECONDS.toMillis(61); + final long thrityOneSecondsAgo = _now - SECONDS.toMillis(31); + + configureMockTransaction(sixtyOneSecondsAgo, thrityOneSecondsAgo); + + _transactionTimeoutHelper.checkIdleOrOpenTimes(_transaction, 0, 0, SECONDS.toMillis(15), SECONDS.toMillis(30)); + + verify(_logActor).message(same(_logSubject), isLogMessage(IDLE_TXN_LOG_HIERARCHY, "CHN-1008 : Idle Transaction : 31,\\d{3} ms")); + verify(_closeAction).doTimeoutAction("Idle transaction timed out"); + } + + public void testIdleAndOpenWarnings() throws Exception + { + final long sixtyOneSecondsAgo = _now - SECONDS.toMillis(61); + final long thirtyOneSecondsAgo = _now - SECONDS.toMillis(31); + + configureMockTransaction(sixtyOneSecondsAgo, thirtyOneSecondsAgo); + + _transactionTimeoutHelper.checkIdleOrOpenTimes(_transaction, SECONDS.toMillis(60), 0, SECONDS.toMillis(30), 0); + + verify(_logActor).message(same(_logSubject), isLogMessage(IDLE_TXN_LOG_HIERARCHY, "CHN-1008 : Idle Transaction : 31,\\d{3} ms")); + verify(_logActor).message(same(_logSubject), isLogMessage(OPEN_TXN_LOG_HIERARCHY, "CHN-1007 : Open Transaction : 61,\\d{3} ms")); + verifyZeroInteractions(_closeAction); + } + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + CurrentActor.set(_logActor); + + _transactionTimeoutHelper = new TransactionTimeoutHelper(_logSubject, _closeAction); + _now = System.currentTimeMillis(); + } + + @Override + protected void tearDown() throws Exception + { + try + { + super.tearDown(); + } + finally + { + CurrentActor.remove(); + } + } + + private void configureMockTransaction(final long startTime, final long updateTime) + { + when(_transaction.isTransactional()).thenReturn(true); + when(_transaction.getTransactionStartTime()).thenReturn(startTime); + when(_transaction.getTransactionUpdateTime()).thenReturn(updateTime); + } + + private LogMessage isLogMessage(String expectedlogHierarchy, String expectedText) + { + return argThat(new IsLogMessage(expectedlogHierarchy, expectedText)); + } + + class IsLogMessage extends ArgumentMatcher + { + private final String _expectedLogHierarchy; + private final String _expectedLogMessageMatches; + private String _hierarchyMatchesFailure; + private String _logMessageMatchesFailure; + + public IsLogMessage(String expectedlogHierarchy, String expectedLogMessageMatches) + { + _expectedLogHierarchy = expectedlogHierarchy; + _expectedLogMessageMatches = expectedLogMessageMatches; + } + + public boolean matches(Object arg) + { + LogMessage logMessage = (LogMessage)arg; + + boolean hierarchyMatches = logMessage.getLogHierarchy().equals(_expectedLogHierarchy); + boolean logMessageMatches = logMessage.toString().matches(_expectedLogMessageMatches); + + if (!hierarchyMatches) + { + _hierarchyMatchesFailure = "LogHierarchy does not match. Expected " + _expectedLogHierarchy + " actual " + logMessage.getLogHierarchy(); + } + + if (!logMessageMatches) + { + _logMessageMatchesFailure = "LogMessage does not match. Expected " + _expectedLogMessageMatches + " actual " + logMessage.toString(); + } + + return hierarchyMatches && logMessageMatches; + } + + @Override + public void describeTo(Description description) + { + if (_hierarchyMatchesFailure != null) + { + description.appendText(_hierarchyMatchesFailure); + } + if (_logMessageMatchesFailure != null) + { + description.appendText(_logMessageMatchesFailure); + } + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/BrokerConfigurationStoreCreatorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/BrokerConfigurationStoreCreatorTest.java new file mode 100644 index 0000000000..a7772ffd10 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/BrokerConfigurationStoreCreatorTest.java @@ -0,0 +1,165 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration; + +import java.io.File; +import java.io.StringWriter; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.configuration.store.JsonConfigurationEntryStore; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Model; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestFileUtils; +import org.apache.qpid.util.FileUtils; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; + +public class BrokerConfigurationStoreCreatorTest extends QpidTestCase +{ + private File _userStoreLocation; + private BrokerConfigurationStoreCreator _storeCreator; + + public void setUp() throws Exception + { + super.setUp(); + + // check whether QPID_HOME JVM system property is set + if (QPID_HOME == null) + { + // set the properties in order to resolve the defaults store settings + setTestSystemProperty("QPID_HOME", TMP_FOLDER); + setTestSystemProperty("QPID_WORK", TMP_FOLDER + File.separator + "work"); + } + _storeCreator = new BrokerConfigurationStoreCreator(); + _userStoreLocation = new File(TMP_FOLDER, "_store_" + System.currentTimeMillis() + "_" + getTestName()); + } + + public void tearDown() throws Exception + { + try + { + super.tearDown(); + } + finally + { + if (_userStoreLocation != null) + { + FileUtils.delete(_userStoreLocation, true); + } + } + } + + public void testCreateJsonStore() + { + ConfigurationEntryStore store = _storeCreator.createStore(_userStoreLocation.getAbsolutePath(), "json", BrokerOptions.DEFAULT_INITIAL_CONFIG_LOCATION, false, new BrokerOptions().getConfigProperties()); + assertNotNull("Store was not created", store); + assertTrue("File should exists", _userStoreLocation.exists()); + assertTrue("File size should be greater than 0", _userStoreLocation.length() > 0); + JsonConfigurationEntryStore jsonStore = new JsonConfigurationEntryStore(_userStoreLocation.getAbsolutePath(), null, false, Collections.emptyMap()); + Set childrenIds = jsonStore.getRootEntry().getChildrenIds(); + assertFalse("Unexpected children: " + childrenIds, childrenIds.isEmpty()); + } + + public void testCreateJsonStoreFromInitialStore() throws Exception + { + createJsonStoreFromInitialStoreTestImpl(false); + } + + public void testOverwriteExistingJsonStoreWithInitialConfig() throws Exception + { + createJsonStoreFromInitialStoreTestImpl(true); + } + + public void createJsonStoreFromInitialStoreTestImpl(boolean overwrite) throws Exception + { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + + String defaultBrokerName = "Broker"; + String testBrokerName = getTestName(); + + Map brokerObjectMap = new HashMap(); + UUID testBrokerId = UUID.randomUUID(); + brokerObjectMap.put(Broker.ID, testBrokerId); + brokerObjectMap.put(Broker.NAME, testBrokerName); + brokerObjectMap.put(Broker.MODEL_VERSION, Model.MODEL_VERSION); + brokerObjectMap.put(Broker.STORE_VERSION, 1); + + StringWriter sw = new StringWriter(); + objectMapper.writeValue(sw, brokerObjectMap); + + String brokerJson = sw.toString(); + + File _initialStoreFile = TestFileUtils.createTempFile(this, ".json", brokerJson); + + ConfigurationEntryStore store = _storeCreator.createStore(_userStoreLocation.getAbsolutePath(), "json", _initialStoreFile.getAbsolutePath(), false, Collections.emptyMap()); + assertNotNull("Store was not created", store); + assertTrue("File should exists", _userStoreLocation.exists()); + assertTrue("File size should be greater than 0", _userStoreLocation.length() > 0); + JsonConfigurationEntryStore jsonStore = new JsonConfigurationEntryStore(_userStoreLocation.getAbsolutePath(), null, false, Collections.emptyMap()); + ConfigurationEntry entry = jsonStore.getRootEntry(); + assertEquals("Unexpected root id", testBrokerId, entry.getId()); + Map attributes = entry.getAttributes(); + assertNotNull("Unexpected attributes: " + attributes, attributes); + assertEquals("Unexpected attributes size: " + attributes.size(), 3, attributes.size()); + assertEquals("Unexpected attribute name: " + attributes.get("name"), testBrokerName, attributes.get(Broker.NAME)); + Set childrenIds = entry.getChildrenIds(); + assertTrue("Unexpected children: " + childrenIds, childrenIds.isEmpty()); + + if(overwrite) + { + ConfigurationEntryStore overwrittenStore = _storeCreator.createStore(_userStoreLocation.getAbsolutePath(), "json", BrokerOptions.DEFAULT_INITIAL_CONFIG_LOCATION, true, new BrokerOptions().getConfigProperties()); + assertNotNull("Store was not created", overwrittenStore); + assertTrue("File should exists", _userStoreLocation.exists()); + assertTrue("File size should be greater than 0", _userStoreLocation.length() > 0); + + //check the contents reflect the test store content having been overwritten with the default store + JsonConfigurationEntryStore reopenedOverwrittenStore = new JsonConfigurationEntryStore(_userStoreLocation.getAbsolutePath(), null, false, Collections.emptyMap()); + entry = reopenedOverwrittenStore.getRootEntry(); + assertFalse("Root id did not change, store content was not overwritten", testBrokerId.equals(entry.getId())); + attributes = entry.getAttributes(); + assertNotNull("No attributes found", attributes); + assertFalse("Test name should not equal default broker name", testBrokerName.equals(defaultBrokerName)); + assertEquals("Unexpected broker name value" , defaultBrokerName, attributes.get(Broker.NAME)); + childrenIds = entry.getChildrenIds(); + assertFalse("Expected children were not found" + childrenIds, childrenIds.isEmpty()); + } + } + + public void testCreateStoreWithUnknownType() + { + try + { + _storeCreator.createStore(_userStoreLocation.getAbsolutePath(), "other", null, false, Collections.emptyMap()); + fail("Store is not yet supported"); + } + catch(IllegalConfigurationException e) + { + // pass + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/BrokerPropertiesTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/BrokerPropertiesTest.java new file mode 100644 index 0000000000..5e9e19ffaf --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/BrokerPropertiesTest.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.configuration; + +import java.util.Locale; + +import org.apache.qpid.test.utils.QpidTestCase; + +public class BrokerPropertiesTest extends QpidTestCase +{ + public void testGetLocaleDefault() + { + Locale locale = BrokerProperties.getLocale(); + assertEquals("Unexpected locale", Locale.US, locale); + } + + public void testGetLocaleSetWithJVMProperty() + { + setTestSystemProperty(BrokerProperties.PROPERTY_LOCALE, "en_GB"); + Locale locale = BrokerProperties.getLocale(); + assertEquals("Unexpected locale", Locale.UK, locale); + } + + public void testGetLocaleSetWithJVMPropertyInUnexpectedFormat() + { + setTestSystemProperty(BrokerProperties.PROPERTY_LOCALE, "penguins_ANTARCTIC_Moubray_Bay"); + Locale locale = BrokerProperties.getLocale(); + assertEquals("Unexpected locale language", "penguins", locale.getLanguage()); + assertEquals("Unexpected locale country", "ANTARCTIC", locale.getCountry()); + assertEquals("Unexpected locale country", "Moubray_Bay", locale.getVariant()); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java new file mode 100644 index 0000000000..c10b3410a5 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/QueueConfigurationTest.java @@ -0,0 +1,291 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 static org.mockito.Mockito.when; + +import java.util.Collections; +import junit.framework.TestCase; +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; + +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.util.BrokerTestHelper; + +public class QueueConfigurationTest extends TestCase +{ + private VirtualHostConfiguration _emptyConf; + private PropertiesConfiguration _env; + private VirtualHostConfiguration _fullHostConf; + private Broker _broker; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _broker = BrokerTestHelper.createBrokerMock(); + _env = new PropertiesConfiguration(); + _emptyConf = new VirtualHostConfiguration("test", _env, _broker); + + PropertiesConfiguration fullEnv = new PropertiesConfiguration(); + fullEnv.setProperty("queues.maximumMessageAge", 1); + fullEnv.setProperty("queues.maximumQueueDepth", 1); + fullEnv.setProperty("queues.maximumMessageSize", 1); + fullEnv.setProperty("queues.maximumMessageCount", 1); + fullEnv.setProperty("queues.minimumAlertRepeatGap", 1); + fullEnv.setProperty("queues.deadLetterQueues", true); + fullEnv.setProperty("queues.maximumDeliveryCount", 5); + + _fullHostConf = new VirtualHostConfiguration("test", fullEnv, _broker); + + } + + @Override + public void tearDown() throws Exception + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + + public void testMaxDeliveryCount() throws Exception + { + // broker MAXIMUM_DELIVERY_ATTEMPTS attribute is not set + when(_broker.getAttribute(Broker.QUEUE_MAXIMUM_DELIVERY_ATTEMPTS)).thenReturn(null); + + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertEquals("Unexpected default server configuration for max delivery count ", 0, qConf.getMaxDeliveryCount()); + + // set broker MAXIMUM_DELIVERY_ATTEMPTS attribute to 2 + when(_broker.getAttribute(Broker.QUEUE_MAXIMUM_DELIVERY_ATTEMPTS)).thenReturn(2); + + // Check that queue inherits the MAXIMUM_DELIVERY_ATTEMPTS value from broker + qConf = new QueueConfiguration("test", _emptyConf); + assertEquals("Unexpected default server configuration for max delivery count ", 2, qConf.getMaxDeliveryCount()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("maximumDeliveryCount", 7); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals("Unexpected host configuration for max delivery count", 7, qConf.getMaxDeliveryCount()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertEquals("Unexpected queue configuration for max delivery count", 5, qConf.getMaxDeliveryCount()); + } + + /** + * Tests that the default setting for DLQ configuration is disabled, and verifies that it can be overridden + * at a broker or virtualhost level. + * @throws Exception + */ + public void testIsDeadLetterQueueEnabled() throws Exception + { + // enable dead letter queues broker wide + when(_broker.getAttribute(Broker.QUEUE_DEAD_LETTER_QUEUE_ENABLED)).thenReturn(true); + + // Check that queue inherits the broker setting + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertTrue("Unexpected queue configuration for dead letter enabled attribute", qConf.isDeadLetterQueueEnabled()); + + // broker DEAD_LETTER_QUEUE_ENABLED is not set + when(_broker.getAttribute(Broker.QUEUE_DEAD_LETTER_QUEUE_ENABLED)).thenReturn(null); + + // Check that queue dead letter queue is not enabled + qConf = new QueueConfiguration("test", _emptyConf); + assertFalse("Unexpected queue configuration for dead letter enabled attribute", qConf.isDeadLetterQueueEnabled()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("deadLetterQueues", true); + qConf = new QueueConfiguration("test", vhostConfig); + assertTrue("Unexpected queue configuration for dead letter enabled attribute", qConf.isDeadLetterQueueEnabled()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertTrue("Unexpected queue configuration for dead letter enabled attribute", qConf.isDeadLetterQueueEnabled()); + } + + public void testGetMaximumMessageAge() throws ConfigurationException + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertEquals(0, qConf.getMaximumMessageAge()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("maximumMessageAge", 2); + + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(2, qConf.getMaximumMessageAge()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertEquals(1, qConf.getMaximumMessageAge()); + } + + public void testGetMaximumQueueDepth() throws ConfigurationException + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertEquals(0, qConf.getMaximumQueueDepth()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("maximumQueueDepth", 2); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(2, qConf.getMaximumQueueDepth()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertEquals(1, qConf.getMaximumQueueDepth()); + } + + public void testGetMaximumMessageSize() throws ConfigurationException + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertEquals(0, qConf.getMaximumMessageSize()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("maximumMessageSize", 2); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(2, qConf.getMaximumMessageSize()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertEquals(1, qConf.getMaximumMessageSize()); + } + + public void testGetMaximumMessageCount() throws ConfigurationException + { + // Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertEquals(0, qConf.getMaximumMessageCount()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("maximumMessageCount", 2); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(2, qConf.getMaximumMessageCount()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertEquals(1, qConf.getMaximumMessageCount()); + } + + public void testGetMinimumAlertRepeatGap() throws Exception + { + // set broker attribute ALERT_REPEAT_GAP to 10 + when(_broker.getAttribute(Broker.QUEUE_ALERT_REPEAT_GAP)).thenReturn(10); + + // check that broker level setting is available on queue configuration + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertEquals(10, qConf.getMinimumAlertRepeatGap()); + + // remove configuration for ALERT_REPEAT_GAP on broker level + when(_broker.getAttribute(Broker.QUEUE_ALERT_REPEAT_GAP)).thenReturn(null); + + // Check default value + qConf = new QueueConfiguration("test", _emptyConf); + assertEquals(0, qConf.getMinimumAlertRepeatGap()); + + // Check explicit value + VirtualHostConfiguration vhostConfig = overrideConfiguration("minimumAlertRepeatGap", 2); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(2, qConf.getMinimumAlertRepeatGap()); + + // Check inherited value + qConf = new QueueConfiguration("test", _fullHostConf); + assertEquals(1, qConf.getMinimumAlertRepeatGap()); + } + + public void testSortQueueConfiguration() throws ConfigurationException + { + //Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertNull(qConf.getQueueSortKey()); + + // Check explicit value + final VirtualHostConfiguration vhostConfig = overrideConfiguration("sortKey", "test-sort-key"); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals("test-sort-key", qConf.getQueueSortKey()); + } + + public void testQueueDescription() throws ConfigurationException + { + //Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertNull(qConf.getDescription()); + + // Check explicit value + final VirtualHostConfiguration vhostConfig = overrideConfiguration("description", "mydescription"); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals("mydescription", qConf.getDescription()); + } + + + public void testQueueSingleArgument() throws ConfigurationException + { + //Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertTrue(qConf.getArguments().isEmpty()); + + // Check explicit value + final VirtualHostConfiguration vhostConfig = overrideConfiguration("argument", "qpid.group_header_key=mykey"); + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(Collections.singletonMap("qpid.group_header_key","mykey"), qConf.getArguments()); + } + + + public void testQueueMultipleArguments() throws ConfigurationException + { + //Check default value + QueueConfiguration qConf = new QueueConfiguration("test", _emptyConf); + assertTrue(qConf.getArguments().isEmpty()); + + + PropertiesConfiguration queueConfig = new PropertiesConfiguration(); + queueConfig.addProperty("queues.queue.test.argument", "qpid.group_header_key=mykey"); + queueConfig.addProperty("queues.queue.test.argument", "qpid.shared_msg_group=1"); + + CompositeConfiguration config = new CompositeConfiguration(); + config.addConfiguration(_fullHostConf.getConfig()); + config.addConfiguration(queueConfig); + + final VirtualHostConfiguration vhostConfig = new VirtualHostConfiguration("test", config, _broker);; + qConf = new QueueConfiguration("test", vhostConfig); + assertEquals(2, qConf.getArguments().size()); + assertEquals("mykey", qConf.getArguments().get("qpid.group_header_key")); + assertEquals("1", qConf.getArguments().get("qpid.shared_msg_group")); + } + + + private VirtualHostConfiguration overrideConfiguration(String property, Object value) + throws ConfigurationException + { + PropertiesConfiguration queueConfig = new PropertiesConfiguration(); + queueConfig.setProperty("queues.queue.test." + property, value); + + CompositeConfiguration config = new CompositeConfiguration(); + config.addConfiguration(_fullHostConf.getConfig()); + config.addConfiguration(queueConfig); + + return new VirtualHostConfiguration("test", config, _broker); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java new file mode 100644 index 0000000000..dc9ddf7b32 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/VirtualHostConfigurationTest.java @@ -0,0 +1,408 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.queue.AMQPriorityQueue; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.qpid.test.utils.QpidTestCase; + +public class VirtualHostConfigurationTest extends QpidTestCase +{ + private VirtualHostRegistry _virtualHostRegistry; + private XMLConfiguration _configXml; + private Broker _broker; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _configXml = new XMLConfiguration(); + _configXml.addProperty("virtualhosts.virtualhost(-1).name", getName()); + _configXml.addProperty("virtualhosts.virtualhost(-1)."+getName()+".store.class", TestableMemoryMessageStore.class.getName()); + _virtualHostRegistry = new VirtualHostRegistry(); + _broker = mock(Broker.class); + when(_broker.getAttribute(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD)).thenReturn(30000l); + } + + @Override + public void tearDown() throws Exception + { + try + { + if (_virtualHostRegistry != null) + { + _virtualHostRegistry.close(); + } + } + finally + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + } + + private XMLConfiguration getConfigXml() + { + return _configXml; + } + + private VirtualHost createVirtualHost(String hostName) throws Exception + { + Configuration config = getConfigXml().subset("virtualhosts.virtualhost." + XmlConfigurationUtilities.escapeTagName(hostName)); + VirtualHostConfiguration virtualHostConfiguration = new VirtualHostConfiguration(hostName, config, _broker); + return BrokerTestHelper.createVirtualHost(virtualHostConfiguration, _virtualHostRegistry); + } + + public void testQueuePriority() throws Exception + { + // Set up queue with 5 priorities + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues(-1).queue(-1).name(-1)", + "atest"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.atest(-1).exchange", + "amq.direct"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.atest.priorities", + "5"); + + // Set up queue with JMS style priorities + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues(-1).queue(-1).name(-1)", + "ptest"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.ptest(-1).exchange", + "amq.direct"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.ptest.priority", + "true"); + + // Set up queue with no priorities + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues(-1).queue(-1).name(-1)", + "ntest"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.ntest(-1).exchange", + "amq.direct"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueuePriority.queues.queue.ntest.priority", + "false"); + + VirtualHost vhost = createVirtualHost(getName()); + + // Check that atest was a priority queue with 5 priorities + AMQQueue atest = vhost.getQueue("atest"); + assertTrue(atest instanceof AMQPriorityQueue); + assertEquals(5, ((AMQPriorityQueue) atest).getPriorities()); + + // Check that ptest was a priority queue with 10 priorities + AMQQueue ptest = vhost.getQueue("ptest"); + assertTrue(ptest instanceof AMQPriorityQueue); + assertEquals(10, ((AMQPriorityQueue) ptest).getPriorities()); + + // Check that ntest wasn't a priority queue + AMQQueue ntest = vhost.getQueue("ntest"); + assertFalse(ntest instanceof AMQPriorityQueue); + } + + public void testQueueAlerts() throws Exception + { + // Set up queue with 5 priorities + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.exchange", "amq.topic"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.maximumQueueDepth", "1"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.maximumMessageSize", "2"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.maximumMessageAge", "3"); + + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues(-1).queue(1).name(1)", "atest"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.queue.atest(-1).exchange", "amq.direct"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.queue.atest(-1).maximumQueueDepth", "4"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.queue.atest(-1).maximumMessageSize", "5"); + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues.queue.atest(-1).maximumMessageAge", "6"); + + getConfigXml().addProperty("virtualhosts.virtualhost.testQueueAlerts.queues(-1).queue(-1).name(-1)", "btest"); + + VirtualHost vhost = createVirtualHost(getName()); + + // Check specifically configured values + AMQQueue aTest = vhost.getQueue("atest"); + assertEquals(4, aTest.getMaximumQueueDepth()); + assertEquals(5, aTest.getMaximumMessageSize()); + assertEquals(6, aTest.getMaximumMessageAge()); + + // Check default values + AMQQueue bTest = vhost.getQueue("btest"); + assertEquals(1, bTest.getMaximumQueueDepth()); + assertEquals(2, bTest.getMaximumMessageSize()); + assertEquals(3, bTest.getMaximumMessageAge()); + } + + public void testMaxDeliveryCount() throws Exception + { + // Set up vhosts and queues + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + ".queues.maximumDeliveryCount", 5); + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + ".queues(-1).queue(-1).name", "biggles"); + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + ".queues.queue.biggles.maximumDeliveryCount", 4); + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + ".queues(-1).queue(-1).name", "beetle"); + + VirtualHost test = createVirtualHost(getName()); + + // Enabled specifically + assertEquals("Test vhost MDC was configured as enabled", 5 ,test.getConfiguration().getMaxDeliveryCount()); + + // Enabled by test vhost default + assertEquals("beetle queue DLQ was configured as enabled", test.getConfiguration().getMaxDeliveryCount(), test.getConfiguration().getQueueConfiguration("beetle").getMaxDeliveryCount()); + + // Disabled specifically + assertEquals("Biggles queue DLQ was configured as disabled", 4, test.getConfiguration().getQueueConfiguration("biggles").getMaxDeliveryCount()); + } + + /** + * Tests the full set of configuration options for enabling DLQs in the broker configuration. + */ + public void testIsDeadLetterQueueEnabled() throws Exception + { + // Set up vhosts and queues + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + ".queues.deadLetterQueues", "true"); + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + ".queues(-1).queue(-1).name", "biggles"); + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + ".queues.queue.biggles.deadLetterQueues", "false"); + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + ".queues(-1).queue(-1).name", "beetle"); + + + getConfigXml().addProperty("virtualhosts.virtualhost.name", getName() + "Extra"); + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + "Extra.queues(-1).queue(-1).name", "r2d2"); + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + "Extra.queues.queue.r2d2.deadLetterQueues", "true"); + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + "Extra.queues(-1).queue(-1).name", "c3p0"); + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + "Extra.store.class", TestableMemoryMessageStore.class.getName()); + + VirtualHost test = createVirtualHost(getName()); + VirtualHost extra = createVirtualHost(getName() + "Extra"); + + // Enabled specifically + assertTrue("Test vhost DLQ was configured as enabled", test.getConfiguration().isDeadLetterQueueEnabled()); + assertTrue("r2d2 queue DLQ was configured as enabled", extra.getConfiguration().getQueueConfiguration("r2d2").isDeadLetterQueueEnabled()); + + // Enabled by test vhost default + assertTrue("beetle queue DLQ was configured as enabled", test.getConfiguration().getQueueConfiguration("beetle").isDeadLetterQueueEnabled()); + + // Disabled specifically + assertFalse("Biggles queue DLQ was configured as disabled", test.getConfiguration().getQueueConfiguration("biggles").isDeadLetterQueueEnabled()); + + // Using broker default of disabled + assertFalse("Extra vhost DLQ disabled, using broker default", extra.getConfiguration().isDeadLetterQueueEnabled()); + assertFalse("c3p0 queue DLQ was configured as disabled", extra.getConfiguration().getQueueConfiguration("c3p0").isDeadLetterQueueEnabled()); + + // Get queues + AMQQueue biggles = test.getQueue("biggles"); + AMQQueue beetle = test.getQueue("beetle"); + AMQQueue r2d2 = extra.getQueue("r2d2"); + AMQQueue c3p0 = extra.getQueue("c3p0"); + + // Disabled specifically for this queue, overriding virtualhost setting + assertNull("Biggles queue should not have alt exchange as DLQ should be configured as disabled: " + biggles.getAlternateExchange(), biggles.getAlternateExchange()); + + // Enabled for all queues on the virtualhost + assertNotNull("Beetle queue should have an alt exchange as DLQ should be enabled, using test vhost default", beetle.getAlternateExchange()); + + // Enabled specifically for this queue, overriding the default broker setting of disabled + assertNotNull("R2D2 queue should have an alt exchange as DLQ should be configured as enabled", r2d2.getAlternateExchange()); + + // Disabled by the default broker setting + assertNull("C3PO queue should not have an alt exchange as DLQ should be disabled, using broker default", c3p0.getAlternateExchange()); + } + + /** + * Test that the house keeping pool sizes is correctly processed + * + * @throws Exception + */ + public void testHouseKeepingThreadCount() throws Exception + { + int initialPoolSize = 10; + + getConfigXml().addProperty("virtualhosts.virtualhost.testHouseKeepingThreadCount.housekeeping.poolSize", + initialPoolSize); + + VirtualHost vhost = createVirtualHost(getName()); + + assertEquals("HouseKeeping PoolSize not set correctly.", + initialPoolSize, vhost.getHouseKeepingPoolSize()); + } + + /** + * Test that we can dynamically change the thread pool size + * + * @throws Exception + */ + public void testDynamicHouseKeepingPoolSizeChange() throws Exception + { + int initialPoolSize = 10; + + getConfigXml().addProperty("virtualhosts.virtualhost.testDynamicHouseKeepingPoolSizeChange.housekeeping.poolSize", + initialPoolSize); + + VirtualHost vhost = createVirtualHost(getName()); + + assertEquals("HouseKeeping PoolSize not set correctly.", + initialPoolSize, vhost.getHouseKeepingPoolSize()); + + vhost.setHouseKeepingPoolSize(1); + + assertEquals("HouseKeeping PoolSize not correctly change.", + 1, vhost.getHouseKeepingPoolSize()); + + } + + /** + * Tests that the old element security.authentication.name is rejected. This element + * was never supported properly as authentication is performed before the virtual host + * is considered. + */ + public void testSecurityAuthenticationNameRejected() throws Exception + { + getConfigXml().addProperty("virtualhosts.virtualhost.testSecurityAuthenticationNameRejected.security.authentication.name", + "testdb"); + + try + { + createVirtualHost(getName()); + fail("Exception not thrown"); + } + catch(ConfigurationException ce) + { + assertEquals("Incorrect error message", + "Validation error : security/authentication/name is no longer a supported element within the configuration xml." + + " It appears in virtual host definition : " + getName(), + ce.getMessage()); + } + } + + /* + * Tests that the old element housekeeping.expiredMessageCheckPeriod. ... (that was + * replaced by housekeeping.checkPeriod) is rejected. + */ + public void testExpiredMessageCheckPeriodRejected() throws Exception + { + getConfigXml().addProperty("virtualhosts.virtualhost.testExpiredMessageCheckPeriodRejected.housekeeping.expiredMessageCheckPeriod", + 5); + + try + { + createVirtualHost(getName()); + fail("Exception not thrown"); + } + catch (ConfigurationException ce) + { + assertEquals("Incorrect error message", + "Validation error : housekeeping/expiredMessageCheckPeriod must be replaced by housekeeping/checkPeriod." + + " It appears in virtual host definition : " + getName(), + ce.getMessage()); + } + } + + /* + * Tests that the queues with dots in the names are fully supported. The XML configuration + * had problems with handling the tags containing dots due to the design of the Apache Commons + * Configuration library. The dots need to be escaped when accessing the XML configuration. + */ + public void testDotsInQueueName() throws Exception + { + // Set up vhosts and queue + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + ".queues(-1).queue(-1).name", "dot.in.a.name"); + // Add a single property which is inside the queue tag - the maximum delivery count + getConfigXml().addProperty("virtualhosts.virtualhost." + getName() + ".queues.queue.dot..in..a..name.maximumDeliveryCount", 5); + + VirtualHost test = createVirtualHost(getName()); + + // Check, that the property stored within the tag has been properly loaded + assertEquals("queue with dots in its name has been properly loaded", 5, test.getConfiguration().getQueueConfiguration("dot.in.a.name").getMaxDeliveryCount()); + } + + /* + * Tests that the virtual hosts with dots in the names are fully supported. The XML + * configuration had problems with handling the tags containing dots due to the design + * of the Apache Commons Configuration library. The dots need to be escaped when + * accessing the XML configuration. + */ + public void testDotsInVirtualHostName() throws Exception + { + // Set up vhosts + getConfigXml().addProperty("virtualhosts.virtualhost.name", "dot.in.a.name"); + // Add a single property which is inside the virtual host tag - the message store + getConfigXml().addProperty("virtualhosts.virtualhost.dot..in..a..name.store.class", TestableMemoryMessageStore.class.getName()); + + VirtualHost test = createVirtualHost("dot.in.a.name"); + + // Check, that the property stored within the tag has been properly loaded + assertEquals("virtual host with dots in the name has been properly loaded", TestableMemoryMessageStore.class.getName(), test.getMessageStore().getClass().getName()); + } + + public void testStoreTransactionIdleTimeoutClose() throws Exception + { + VirtualHost vhost = createVirtualHost(getName()); + assertEquals("Unexpected StoreTransactionIdleTimeoutClose value", 0, vhost.getConfiguration().getTransactionTimeoutIdleClose()); + + when(_broker.getAttribute(Broker.VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_CLOSE)).thenReturn(1000l); + assertEquals("Unexpected StoreTransactionIdleTimeoutClose value", 1000l, vhost.getConfiguration().getTransactionTimeoutIdleClose()); + + vhost.getConfiguration().getConfig().setProperty("transactionTimeout.idleClose", 2000l); + assertEquals("Unexpected StoreTransactionIdleTimeoutClose value", 2000l, vhost.getConfiguration().getTransactionTimeoutIdleClose()); + } + + public void testStoreTransactionIdleTimeoutWarn() throws Exception + { + VirtualHost vhost = createVirtualHost(getName()); + assertEquals("Unexpected StoreTransactionIdleTimeoutWarn value", 0, vhost.getConfiguration().getTransactionTimeoutIdleWarn()); + + when(_broker.getAttribute(Broker.VIRTUALHOST_STORE_TRANSACTION_IDLE_TIMEOUT_WARN)).thenReturn(1000l); + assertEquals("Unexpected StoreTransactionIdleTimeoutWarn value", 1000l, vhost.getConfiguration().getTransactionTimeoutIdleWarn()); + + vhost.getConfiguration().getConfig().setProperty("transactionTimeout.idleWarn", 2000l); + assertEquals("Unexpected StoreTransactionIdleTimeoutWarn value", 2000l, vhost.getConfiguration().getTransactionTimeoutIdleWarn()); + } + + public void testStoreTransactionOpenTimeoutClose() throws Exception + { + VirtualHost vhost = createVirtualHost(getName()); + assertEquals("Unexpected StoreTransactionOpenTimeoutClose value", 0, vhost.getConfiguration().getTransactionTimeoutOpenClose()); + + when(_broker.getAttribute(Broker.VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_CLOSE)).thenReturn(1000l); + assertEquals("Unexpected StoreTransactionOpenTimeoutClose value", 1000l, vhost.getConfiguration().getTransactionTimeoutOpenClose()); + + vhost.getConfiguration().getConfig().setProperty("transactionTimeout.openClose", 2000l); + assertEquals("Unexpected StoreTransactionOpenTimeoutClose value", 2000l, vhost.getConfiguration().getTransactionTimeoutOpenClose()); + } + + public void testStoreTransactionOpenTimeoutWarn() throws Exception + { + VirtualHost vhost = createVirtualHost(getName()); + assertEquals("Unexpected StoreTransactionOpenTimeoutWarn value", 0, vhost.getConfiguration().getTransactionTimeoutOpenWarn()); + + when(_broker.getAttribute(Broker.VIRTUALHOST_STORE_TRANSACTION_OPEN_TIMEOUT_WARN)).thenReturn(1000l); + assertEquals("Unexpected StoreTransactionOpenTimeoutWarn value", 1000l, vhost.getConfiguration().getTransactionTimeoutOpenWarn()); + + vhost.getConfiguration().getConfig().setProperty("transactionTimeout.openWarn", 2000l); + assertEquals("Unexpected StoreTransactionOpenTimeoutWarn value", 2000l, vhost.getConfiguration().getTransactionTimeoutOpenWarn()); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/plugins/AbstractConfigurationTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/plugins/AbstractConfigurationTest.java new file mode 100644 index 0000000000..674abbfeb7 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/plugins/AbstractConfigurationTest.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.configuration.plugins; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.XMLConfiguration; + +import org.apache.qpid.test.utils.QpidTestCase; + +import java.util.List; + +/** + * Test that verifies that given a Configuration a ConfigurationPlugin can + * process and validate that data. + */ +public class AbstractConfigurationTest extends QpidTestCase +{ + private static final double DOUBLE = 3.14; + private static final long POSITIVE_LONG = 1000; + private static final long NEGATIVE_LONG = -1000; + private static final int LIST_SIZE = 3; + + class TestConfigPlugin extends AbstractConfiguration + { + @Override + public String[] getElementsProcessed() + { + return new String[]{"[@property]", "name", + "positiveLong", "negativeLong", + "true", "list", "double"}; + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + // no validation requried + } + + public String getName() + { + return getStringValue("name"); + } + + public String getProperty() + { + return getStringValue("[@property]"); + } + + + } + + private TestConfigPlugin _plugin; + + @Override + public void setUp() throws Exception + { + // Test does not directly use the AppRegistry but the configured broker + // is required for the correct ConfigurationPlugin processing + super.setUp(); + XMLConfiguration xmlconfig = new XMLConfiguration(); + xmlconfig.addProperty("base.element[@property]", "property"); + xmlconfig.addProperty("base.element.name", "name"); + // We make these strings as that is how they will be read from the file. + xmlconfig.addProperty("base.element.positiveLong", String.valueOf(POSITIVE_LONG)); + xmlconfig.addProperty("base.element.negativeLong", String.valueOf(NEGATIVE_LONG)); + xmlconfig.addProperty("base.element.boolean", String.valueOf(true)); + xmlconfig.addProperty("base.element.double", String.valueOf(DOUBLE)); + for (int i = 0; i < LIST_SIZE; i++) + { + xmlconfig.addProperty("base.element.list", i); + } + + //Use a composite configuration as this is what our broker code uses. + CompositeConfiguration composite = new CompositeConfiguration(); + composite.addConfiguration(xmlconfig); + + _plugin = new TestConfigPlugin(); + + try + { + _plugin.setConfiguration("base.element", composite.subset("base.element")); + } + catch (ConfigurationException e) + { + e.printStackTrace(); + fail(e.toString()); + } + + } + + public void testHasConfiguration() + { + assertTrue("Plugin has no configuration ", _plugin.hasConfiguration()); + _plugin = new TestConfigPlugin(); + assertFalse("Plugins has configuration", _plugin.hasConfiguration()); + } + + public void testValuesRetreived() + { + assertEquals("Name not correct", "name", _plugin.getName()); + assertEquals("Property not correct", "property", _plugin.getProperty()); + } + + public void testContainsPositiveLong() + { + assertTrue("positiveLong is not positive", _plugin.containsPositiveLong("positiveLong")); + assertFalse("NonExistentValue was found", _plugin.containsPositiveLong("NonExistentValue")); + + try + { + _plugin.validatePositiveLong("positiveLong"); + } + catch (ConfigurationException e) + { + fail(e.getMessage()); + } + + try + { + _plugin.validatePositiveLong("negativeLong"); + fail("negativeLong should not be positive"); + } + catch (ConfigurationException e) + { + assertEquals("negativeLong should not be reported as positive", + "TestConfigPlugin: unable to configure invalid negativeLong:" + NEGATIVE_LONG, e.getMessage()); + } + + } + + public void testDouble() + { + assertEquals("Double value not returned", DOUBLE, _plugin.getDoubleValue("double")); + assertEquals("default Double value not returned", 0.0, _plugin.getDoubleValue("NonExistent")); + assertEquals("set default Double value not returned", DOUBLE, _plugin.getDoubleValue("NonExistent", DOUBLE)); + } + + public void testLong() + { + assertTrue("Long value not returned", _plugin.containsLong("positiveLong")); + assertFalse("Long value returned", _plugin.containsLong("NonExistent")); + assertEquals("Long value not returned", POSITIVE_LONG, _plugin.getLongValue("positiveLong")); + assertEquals("default Long value not returned", 0, _plugin.getLongValue("NonExistent")); + assertEquals("set default Long value not returned", NEGATIVE_LONG, _plugin.getLongValue("NonExistent", NEGATIVE_LONG)); + } + + public void testInt() + { + assertTrue("Int value not returned", _plugin.containsInt("positiveLong")); + assertFalse("Int value returned", _plugin.containsInt("NonExistent")); + assertEquals("Int value not returned", (int) POSITIVE_LONG, _plugin.getIntValue("positiveLong")); + assertEquals("default Int value not returned", 0, _plugin.getIntValue("NonExistent")); + assertEquals("set default Int value not returned", (int) NEGATIVE_LONG, _plugin.getIntValue("NonExistent", (int) NEGATIVE_LONG)); + } + + public void testString() + { + assertEquals("String value not returned", "name", _plugin.getStringValue("name")); + assertNull("Null default String value not returned", _plugin.getStringValue("NonExistent", null)); + assertNull("default String value not returned", _plugin.getStringValue("NonExistent")); + assertEquals("default String value not returned", "Default", _plugin.getStringValue("NonExistent", "Default")); + } + + public void testBoolean() + { + assertTrue("Boolean value not returned", _plugin.containsBoolean("boolean")); + assertFalse("Boolean value not returned", _plugin.containsBoolean("NonExistent")); + assertTrue("Boolean value not returned", _plugin.getBooleanValue("boolean")); + assertFalse("default String value not returned", _plugin.getBooleanValue("NonExistent")); + assertTrue("set default String value not returned", _plugin.getBooleanValue("NonExistent", true)); + } + + public void testList() + { + assertTrue("list not found in plugin", _plugin.contains("list")); + List list = _plugin.getListValue("list"); + assertNotNull("Returned list should not be null", list); + assertEquals("List should not be empty", LIST_SIZE, list.size()); + + list = _plugin.getListValue("NonExistent"); + assertNotNull("Returned list should not be null", list); + assertEquals("List is not empty", 0, list.size()); + } + + public void testContains() + { + assertTrue("list not found in plugin", _plugin.contains("list")); + assertFalse("NonExistent found in plugin", _plugin.contains("NonExistent")); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecovererTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecovererTest.java new file mode 100644 index 0000000000..eed54ef5bf --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/AuthenticationProviderRecovererTest.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.configuration.startup; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.configuration.store.StoreConfigurationChangeListener; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.model.adapter.AuthenticationProviderFactory; +import org.apache.qpid.server.model.adapter.FileSystemPreferencesProvider; +import org.apache.qpid.server.model.adapter.PreferencesProviderCreator; +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.security.auth.manager.PlainPasswordFileAuthenticationManagerFactory; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestFileUtils; + +public class AuthenticationProviderRecovererTest extends QpidTestCase +{ + private Broker _broker; + private AuthenticationProviderRecoverer _recoverer; + private ConfigurationEntryStore _configurationStore; + private PreferencesProviderCreator _preferencesProviderCreator; + + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _broker = BrokerTestHelper.createBrokerMock(); + _preferencesProviderCreator = new PreferencesProviderCreator(); + QpidServiceLoader serviceLoader = new QpidServiceLoader(); + AuthenticationProviderFactory authenticationProviderFactory = new AuthenticationProviderFactory(serviceLoader, _preferencesProviderCreator); + StoreConfigurationChangeListener storeChangeListener = mock(StoreConfigurationChangeListener.class); + _recoverer = new AuthenticationProviderRecoverer(authenticationProviderFactory, storeChangeListener); + _configurationStore = mock(ConfigurationEntryStore.class); + } + + public void tearDown() throws Exception + { + try + { + BrokerTestHelper.tearDown(); + } + finally + { + super.tearDown(); + } + } + + public void testRecoverAuthenticationProviderWithPreferencesProvider() + { + File authenticationProviderFile = TestFileUtils.createTempFile(this, "test-authenticator.txt", "test_user:test_user"); + try + { + Map authenticationAttributes = new HashMap(); + authenticationAttributes.put(AuthenticationManagerFactory.ATTRIBUTE_TYPE, + PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE); + authenticationAttributes.put(AuthenticationProvider.NAME, "test-authenticator"); + authenticationAttributes.put(PlainPasswordFileAuthenticationManagerFactory.ATTRIBUTE_PATH, + authenticationProviderFile.getAbsolutePath()); + UUID authenticationId = UUID.randomUUID(); + + final PreferencesProviderRecoverer preferencesRecoverer = new PreferencesProviderRecoverer(_preferencesProviderCreator); + RecovererProvider recovererProvider = new RecovererProvider() + { + @Override + public ConfiguredObjectRecoverer getRecoverer(String type) + { + return preferencesRecoverer; + } + }; + + Map preferencesAttributes = new HashMap(); + UUID preferencesId = UUID.randomUUID(); + preferencesAttributes.put(PreferencesProvider.TYPE, FileSystemPreferencesProvider.class); + preferencesAttributes.put(PreferencesProvider.NAME, "test-provider"); + File file = TestFileUtils.createTempFile(this, ".prefs.json", + "{\"test_user\":{\"pref1\": \"pref1Value\", \"pref2\": 1.0} }"); + preferencesAttributes.put(FileSystemPreferencesProvider.PATH, file.getAbsolutePath()); + ConfigurationEntry preferencesEntry = new ConfigurationEntry(preferencesId, PreferencesProvider.class.getSimpleName(), + preferencesAttributes, Collections. emptySet(), _configurationStore); + when(_configurationStore.getEntry(preferencesId)).thenReturn(preferencesEntry); + + ConfigurationEntry authenticationProviderEntry = new ConfigurationEntry(authenticationId, + AuthenticationProvider.class.getSimpleName(), authenticationAttributes, Collections.singleton(preferencesId), + _configurationStore); + try + { + AuthenticationProvider authenticationProvider = _recoverer.create(recovererProvider, authenticationProviderEntry, + _broker); + assertNotNull("Authentication provider was not recovered", authenticationProvider); + assertEquals("Unexpected name", "test-authenticator", authenticationProvider.getName()); + assertEquals("Unexpected id", authenticationId, authenticationProvider.getId()); + PreferencesProvider preferencesProvider = authenticationProvider.getPreferencesProvider(); + assertNotNull("Preferences provider was not recovered", preferencesProvider); + assertEquals("Unexpected path", file.getAbsolutePath(), + preferencesProvider.getAttribute(FileSystemPreferencesProvider.PATH)); + } + finally + { + file.delete(); + } + } + finally + { + authenticationProviderFile.delete(); + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/BrokerRecovererTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/BrokerRecovererTest.java new file mode 100644 index 0000000000..e6db5af222 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/BrokerRecovererTest.java @@ -0,0 +1,394 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.startup; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.UUID; + +import junit.framework.TestCase; + +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.logging.LogRecorder; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Model; +import org.apache.qpid.server.model.Plugin; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.model.adapter.AccessControlProviderFactory; +import org.apache.qpid.server.model.adapter.AuthenticationProviderFactory; +import org.apache.qpid.server.model.adapter.GroupProviderFactory; +import org.apache.qpid.server.model.adapter.PortFactory; +import org.apache.qpid.server.model.adapter.PreferencesProviderCreator; +import org.apache.qpid.server.configuration.store.StoreConfigurationChangeListener; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public class BrokerRecovererTest extends TestCase +{ + private BrokerRecoverer _brokerRecoverer; + private ConfigurationEntry _brokerEntry = mock(ConfigurationEntry.class); + + private UUID _brokerId = UUID.randomUUID(); + private Map> _brokerEntryChildren = new HashMap>(); + private ConfigurationEntry _authenticationProviderEntry1; + private AuthenticationProvider _authenticationProvider1; + private UUID _authenticationProvider1Id = UUID.randomUUID(); + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + _brokerRecoverer = new BrokerRecoverer(mock(AuthenticationProviderFactory.class), mock(GroupProviderFactory.class), mock(AccessControlProviderFactory.class), mock(PortFactory.class), + mock(PreferencesProviderCreator.class), + mock(StatisticsGatherer.class), mock(VirtualHostRegistry.class), mock(LogRecorder.class), mock(RootMessageLogger.class), mock(TaskExecutor.class), mock(BrokerOptions.class), + mock(StoreConfigurationChangeListener.class)); + when(_brokerEntry.getId()).thenReturn(_brokerId); + when(_brokerEntry.getChildren()).thenReturn(_brokerEntryChildren); + when(_brokerEntry.getAttributes()).thenReturn(Collections.singletonMap(Broker.MODEL_VERSION, Model.MODEL_VERSION)); + + //Add a base AuthenticationProvider for all tests + _authenticationProvider1 = mock(AuthenticationProvider.class); + when(_authenticationProvider1.getName()).thenReturn("authenticationProvider1"); + when(_authenticationProvider1.getId()).thenReturn(_authenticationProvider1Id); + _authenticationProviderEntry1 = mock(ConfigurationEntry.class); + _brokerEntryChildren.put(AuthenticationProvider.class.getSimpleName(), Arrays.asList(_authenticationProviderEntry1)); + } + + public void testCreateBrokerAttributes() + { + Map attributes = new HashMap(); + attributes.put(Broker.DEFAULT_VIRTUAL_HOST, "test"); + attributes.put(Broker.QUEUE_ALERT_THRESHOLD_MESSAGE_AGE, 9l); + attributes.put(Broker.QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, 8l); + attributes.put(Broker.QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, 7l); + attributes.put(Broker.QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE, 6l); + attributes.put(Broker.QUEUE_ALERT_REPEAT_GAP, 5l); + attributes.put(Broker.QUEUE_FLOW_CONTROL_SIZE_BYTES, 5l); + attributes.put(Broker.QUEUE_FLOW_CONTROL_RESUME_SIZE_BYTES, 3l); + attributes.put(Broker.QUEUE_MAXIMUM_DELIVERY_ATTEMPTS, 2); + attributes.put(Broker.QUEUE_DEAD_LETTER_QUEUE_ENABLED, true); + attributes.put(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD, 1l); + attributes.put(Broker.CONNECTION_SESSION_COUNT_LIMIT, 1000); + attributes.put(Broker.CONNECTION_HEART_BEAT_DELAY, 2000); + attributes.put(Broker.STATISTICS_REPORTING_PERIOD, 4000); + attributes.put(Broker.STATISTICS_REPORTING_RESET_ENABLED, true); + attributes.put(Broker.MODEL_VERSION, Model.MODEL_VERSION); + + Map entryAttributes = new HashMap(); + for (Map.Entry attribute : attributes.entrySet()) + { + String value = convertToString(attribute.getValue()); + entryAttributes.put(attribute.getKey(), value); + } + + when(_brokerEntry.getAttributes()).thenReturn(entryAttributes); + + final ConfigurationEntry virtualHostEntry = mock(ConfigurationEntry.class); + String typeName = VirtualHost.class.getSimpleName(); + when(virtualHostEntry.getType()).thenReturn(typeName); + _brokerEntryChildren.put(typeName, Arrays.asList(virtualHostEntry)); + final VirtualHost virtualHost = mock(VirtualHost.class); + when(virtualHost.getName()).thenReturn("test"); + + RecovererProvider recovererProvider = createRecoveryProvider(new ConfigurationEntry[] { virtualHostEntry, _authenticationProviderEntry1 }, + new ConfiguredObject[] { virtualHost, _authenticationProvider1 }); + Broker broker = _brokerRecoverer.create(recovererProvider, _brokerEntry); + assertNotNull(broker); + assertEquals(_brokerId, broker.getId()); + + for (Map.Entry attribute : attributes.entrySet()) + { + Object attributeValue = broker.getAttribute(attribute.getKey()); + assertEquals("Unexpected value of attribute '" + attribute.getKey() + "'", attribute.getValue(), attributeValue); + } + } + + public void testCreateBrokerWithVirtualHost() + { + final ConfigurationEntry virtualHostEntry = mock(ConfigurationEntry.class); + + String typeName = VirtualHost.class.getSimpleName(); + when(virtualHostEntry.getType()).thenReturn(typeName); + _brokerEntryChildren.put(typeName, Arrays.asList(virtualHostEntry)); + + final VirtualHost virtualHost = mock(VirtualHost.class); + + RecovererProvider recovererProvider = createRecoveryProvider(new ConfigurationEntry[]{virtualHostEntry, _authenticationProviderEntry1}, + new ConfiguredObject[]{virtualHost, _authenticationProvider1}); + + Broker broker = _brokerRecoverer.create(recovererProvider, _brokerEntry); + + assertNotNull(broker); + assertEquals(_brokerId, broker.getId()); + assertEquals(1, broker.getVirtualHosts().size()); + assertEquals(virtualHost, broker.getVirtualHosts().iterator().next()); + } + + public void testCreateBrokerWithPorts() + { + ConfigurationEntry portEntry = mock(ConfigurationEntry.class); + Port port = mock(Port.class); + _brokerEntryChildren.put(Port.class.getSimpleName(), Arrays.asList(portEntry)); + + RecovererProvider recovererProvider = createRecoveryProvider(new ConfigurationEntry[]{portEntry, _authenticationProviderEntry1}, + new ConfiguredObject[]{port, _authenticationProvider1}); + + Broker broker = _brokerRecoverer.create(recovererProvider, _brokerEntry); + + assertNotNull(broker); + assertEquals(_brokerId, broker.getId()); + assertEquals(Collections.singletonList(port), broker.getPorts()); + } + + public void testCreateBrokerWithOneAuthenticationProvider() + { + RecovererProvider recovererProvider = createRecoveryProvider(new ConfigurationEntry[]{_authenticationProviderEntry1}, + new ConfiguredObject[]{_authenticationProvider1}); + + Broker broker = _brokerRecoverer.create(recovererProvider, _brokerEntry); + + assertNotNull(broker); + assertEquals(_brokerId, broker.getId()); + assertEquals(Collections.singletonList(_authenticationProvider1), broker.getAuthenticationProviders()); + } + + public void testCreateBrokerWithMultipleAuthenticationProvidersAndPorts() + { + //Create a second authentication provider + AuthenticationProvider authenticationProvider2 = mock(AuthenticationProvider.class); + when(authenticationProvider2.getName()).thenReturn("authenticationProvider2"); + ConfigurationEntry authenticationProviderEntry2 = mock(ConfigurationEntry.class); + _brokerEntryChildren.put(AuthenticationProvider.class.getSimpleName(), Arrays.asList(_authenticationProviderEntry1, authenticationProviderEntry2)); + + //Add a couple ports + ConfigurationEntry portEntry1 = mock(ConfigurationEntry.class); + Port port1 = mock(Port.class); + when(port1.getId()).thenReturn(UUIDGenerator.generateRandomUUID()); + when(port1.getName()).thenReturn("port1"); + when(port1.getPort()).thenReturn(5671); + when(port1.getAttribute(Port.AUTHENTICATION_PROVIDER)).thenReturn("authenticationProvider1"); + ConfigurationEntry portEntry2 = mock(ConfigurationEntry.class); + Port port2 = mock(Port.class); + when(port2.getId()).thenReturn(UUIDGenerator.generateRandomUUID()); + when(port2.getName()).thenReturn("port2"); + when(port2.getPort()).thenReturn(5672); + when(port2.getAttribute(Port.AUTHENTICATION_PROVIDER)).thenReturn("authenticationProvider2"); + _brokerEntryChildren.put(Port.class.getSimpleName(), Arrays.asList(portEntry1, portEntry2)); + + RecovererProvider recovererProvider = createRecoveryProvider( + new ConfigurationEntry[]{portEntry1, portEntry2, authenticationProviderEntry2, _authenticationProviderEntry1}, + new ConfiguredObject[]{port1, port2, authenticationProvider2, _authenticationProvider1}); + + Broker broker = _brokerRecoverer.create(recovererProvider, _brokerEntry); + + assertNotNull(broker); + assertEquals("Unexpected number of authentication providers", 2, broker.getAuthenticationProviders().size()); + + Collection ports = broker.getPorts(); + assertEquals("Unexpected number of ports", 2, ports.size()); + assertTrue(ports.contains(port1)); + assertTrue(ports.contains(port2)); + } + + public void testCreateBrokerWithGroupProvider() + { + ConfigurationEntry groupProviderEntry = mock(ConfigurationEntry.class); + GroupProvider groupProvider = mock(GroupProvider.class); + _brokerEntryChildren.put(GroupProvider.class.getSimpleName(), Arrays.asList(groupProviderEntry)); + + RecovererProvider recovererProvider = createRecoveryProvider(new ConfigurationEntry[]{groupProviderEntry, _authenticationProviderEntry1}, + new ConfiguredObject[]{groupProvider, _authenticationProvider1}); + + Broker broker = _brokerRecoverer.create(recovererProvider, _brokerEntry); + + assertNotNull(broker); + assertEquals(_brokerId, broker.getId()); + assertEquals(Collections.singletonList(groupProvider), broker.getGroupProviders()); + } + + public void testCreateBrokerWithPlugins() + { + ConfigurationEntry pluginEntry = mock(ConfigurationEntry.class); + Plugin plugin = mock(Plugin.class); + _brokerEntryChildren.put(Plugin.class.getSimpleName(), Arrays.asList(pluginEntry)); + + RecovererProvider recovererProvider = createRecoveryProvider(new ConfigurationEntry[]{pluginEntry, _authenticationProviderEntry1}, + new ConfiguredObject[]{plugin, _authenticationProvider1}); + + Broker broker = _brokerRecoverer.create(recovererProvider, _brokerEntry); + + assertNotNull(broker); + assertEquals(_brokerId, broker.getId()); + assertEquals(Collections.singleton(plugin), new HashSet(broker.getChildren(Plugin.class))); + } + + public void testCreateBrokerWithKeyStores() + { + ConfigurationEntry pluginEntry = mock(ConfigurationEntry.class); + KeyStore keyStore = mock(KeyStore.class); + _brokerEntryChildren.put(KeyStore.class.getSimpleName(), Arrays.asList(pluginEntry)); + + RecovererProvider recovererProvider = createRecoveryProvider(new ConfigurationEntry[]{pluginEntry, _authenticationProviderEntry1}, + new ConfiguredObject[]{keyStore, _authenticationProvider1}); + + Broker broker = _brokerRecoverer.create(recovererProvider, _brokerEntry); + + assertNotNull(broker); + assertEquals(_brokerId, broker.getId()); + assertEquals(Collections.singleton(keyStore), new HashSet(broker.getChildren(KeyStore.class))); + } + + public void testCreateBrokerWithTrustStores() + { + ConfigurationEntry pluginEntry = mock(ConfigurationEntry.class); + TrustStore trustStore = mock(TrustStore.class); + _brokerEntryChildren.put(TrustStore.class.getSimpleName(), Arrays.asList(pluginEntry)); + + RecovererProvider recovererProvider = createRecoveryProvider(new ConfigurationEntry[]{pluginEntry, _authenticationProviderEntry1}, + new ConfiguredObject[]{trustStore, _authenticationProvider1}); + + Broker broker = _brokerRecoverer.create(recovererProvider, _brokerEntry); + + assertNotNull(broker); + assertEquals(_brokerId, broker.getId()); + assertEquals(Collections.singleton(trustStore), new HashSet(broker.getChildren(TrustStore.class))); + } + + public void testModelVersionValidationForIncompatibleMajorVersion() throws Exception + { + Map brokerAttributes = new HashMap(); + String[] incompatibleVersions = {Integer.MAX_VALUE + "." + 0, "0.0"}; + for (String incompatibleVersion : incompatibleVersions) + { + brokerAttributes.put(Broker.MODEL_VERSION, incompatibleVersion); + when(_brokerEntry.getAttributes()).thenReturn(brokerAttributes); + + try + { + _brokerRecoverer.create(null, _brokerEntry); + fail("The broker creation should fail due to unsupported model version"); + } + catch (IllegalConfigurationException e) + { + assertEquals("The model version '" + incompatibleVersion + + "' in configuration is incompatible with the broker model version '" + Model.MODEL_VERSION + "'", e.getMessage()); + } + } + } + + + public void testModelVersionValidationForIncompatibleMinorVersion() throws Exception + { + Map brokerAttributes = new HashMap(); + String incompatibleVersion = Model.MODEL_MAJOR_VERSION + "." + Integer.MAX_VALUE; + brokerAttributes.put(Broker.MODEL_VERSION, incompatibleVersion); + when(_brokerEntry.getAttributes()).thenReturn(brokerAttributes); + + try + { + _brokerRecoverer.create(null, _brokerEntry); + fail("The broker creation should fail due to unsupported model version"); + } + catch (IllegalConfigurationException e) + { + assertEquals("The model version '" + incompatibleVersion + + "' in configuration is incompatible with the broker model version '" + Model.MODEL_VERSION + "'", e.getMessage()); + } + } + + public void testIncorrectModelVersion() throws Exception + { + Map brokerAttributes = new HashMap(); + String[] versions = { Integer.MAX_VALUE + "_" + 0, "", null }; + for (String modelVersion : versions) + { + brokerAttributes.put(Broker.MODEL_VERSION, modelVersion); + when(_brokerEntry.getAttributes()).thenReturn(brokerAttributes); + + try + { + _brokerRecoverer.create(null, _brokerEntry); + fail("The broker creation should fail due to unsupported model version"); + } + catch (IllegalConfigurationException e) + { + // pass + } + } + } + + private String convertToString(Object attributeValue) + { + return String.valueOf(attributeValue); + } + + private RecovererProvider createRecoveryProvider(final ConfigurationEntry[] entries, final ConfiguredObject[] objectsToRecoverer) + { + RecovererProvider recovererProvider = new RecovererProvider() + { + @Override + public ConfiguredObjectRecoverer getRecoverer(String type) + { + @SuppressWarnings({ "unchecked", "rawtypes" }) + final ConfiguredObjectRecoverer recovever = new ConfiguredObjectRecoverer() + { + @Override + public ConfiguredObject create(RecovererProvider recovererProvider, ConfigurationEntry entry, ConfiguredObject... parents) + { + for (int i = 0; i < entries.length; i++) + { + ConfigurationEntry e = entries[i]; + if (entry == e) + { + return objectsToRecoverer[i]; + } + } + return null; + } + }; + + return recovever; + } + }; + return recovererProvider; + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProviderTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProviderTest.java new file mode 100644 index 0000000000..9e2f2fcf32 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/DefaultRecovererProviderTest.java @@ -0,0 +1,69 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.startup; + +import static org.mockito.Mockito.mock; +import junit.framework.TestCase; + +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.logging.LogRecorder; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Plugin; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.configuration.store.StoreConfigurationChangeListener; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public class DefaultRecovererProviderTest extends TestCase +{ + public void testGetRecoverer() + { + String[] supportedTypes = {Broker.class.getSimpleName(), + VirtualHost.class.getSimpleName(), AuthenticationProvider.class.getSimpleName(), + GroupProvider.class.getSimpleName(), Plugin.class.getSimpleName(), Port.class.getSimpleName(), + KeyStore.class.getSimpleName(), TrustStore.class.getSimpleName()}; + + // mocking the required object + StatisticsGatherer statisticsGatherer = mock(StatisticsGatherer.class); + VirtualHostRegistry virtualHostRegistry = mock(VirtualHostRegistry.class); + LogRecorder logRecorder = mock(LogRecorder.class); + RootMessageLogger rootMessageLogger = mock(RootMessageLogger.class); + TaskExecutor taskExecutor = mock(TaskExecutor.class); + + DefaultRecovererProvider provider = new DefaultRecovererProvider(statisticsGatherer, virtualHostRegistry, + logRecorder, rootMessageLogger, taskExecutor, mock(BrokerOptions.class), + mock(StoreConfigurationChangeListener.class)); + for (String configuredObjectType : supportedTypes) + { + ConfiguredObjectRecoverer recovever = provider.getRecoverer(configuredObjectType); + assertNotNull("Null recoverer for type: " + configuredObjectType, recovever); + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/GroupProviderRecovererTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/GroupProviderRecovererTest.java new file mode 100644 index 0000000000..d6f03a9758 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/GroupProviderRecovererTest.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.configuration.startup; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.model.adapter.GroupProviderFactory; +import org.apache.qpid.server.plugin.GroupManagerFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.security.group.GroupManager; + +import junit.framework.TestCase; + +public class GroupProviderRecovererTest extends TestCase +{ + + private UUID _id; + private Map _attributes; + + private GroupManagerFactory _factory; + private QpidServiceLoader _groupManagerServiceLoader; + private Broker _broker; + private ConfigurationEntry _configurationEntry; + private GroupProviderFactory _groupProviderFactory; + + @SuppressWarnings("unchecked") + protected void setUp() throws Exception + { + super.setUp(); + _id = UUID.randomUUID(); + _attributes = new HashMap(); + + _factory = mock(GroupManagerFactory.class); + + _groupManagerServiceLoader = mock(QpidServiceLoader.class); + when(_groupManagerServiceLoader.instancesOf(GroupManagerFactory.class)).thenReturn(Collections.singletonList(_factory )); + _groupProviderFactory = new GroupProviderFactory(_groupManagerServiceLoader); + + _broker = mock(Broker.class); + + _configurationEntry = mock(ConfigurationEntry.class); + when(_configurationEntry.getId()).thenReturn(_id); + when(_configurationEntry.getAttributes()).thenReturn(_attributes); + } + + public void testCreate() + { + GroupManager groupManager = mock(GroupManager.class); + String name = groupManager.getClass().getSimpleName(); + _attributes.put(GroupProvider.NAME, name); + when(_factory.createInstance(_attributes)).thenReturn(groupManager); + GroupProviderRecoverer groupProviderRecoverer = new GroupProviderRecoverer(_groupProviderFactory); + GroupProvider groupProvider = groupProviderRecoverer.create(null, _configurationEntry, _broker); + assertNotNull("Null group provider", groupProvider); + assertEquals("Unexpected name", name, groupProvider.getName()); + assertEquals("Unexpected ID", _id, groupProvider.getId()); + } + + public void testCreateThrowsExceptionWhenNoGroupManagerIsCreated() + { + when(_factory.createInstance(_attributes)).thenReturn(null); + + GroupProviderRecoverer groupProviderRecoverer = new GroupProviderRecoverer(_groupProviderFactory); + try + { + groupProviderRecoverer.create(null, _configurationEntry, _broker); + fail("Configuration exception should be thrown when group manager is not created"); + } + catch(IllegalConfigurationException e) + { + // pass + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/KeyStoreRecovererTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/KeyStoreRecovererTest.java new file mode 100644 index 0000000000..e0a736df89 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/KeyStoreRecovererTest.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.startup; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.net.ssl.KeyManagerFactory; + +import junit.framework.TestCase; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.adapter.AbstractKeyStoreAdapter; +import org.apache.qpid.test.utils.TestSSLConstants; + +public class KeyStoreRecovererTest extends TestCase +{ + + public void testCreateWithAllAttributesProvided() + { + Map attributes = getKeyStoreAttributes(); + Map attributesCopy = new HashMap(attributes); + + UUID id = UUID.randomUUID(); + Broker broker = mock(Broker.class); + ConfigurationEntry entry = mock(ConfigurationEntry.class); + when(entry.getAttributes()).thenReturn(attributes); + when(entry.getId()).thenReturn(id); + + KeyStoreRecoverer recovever = new KeyStoreRecoverer(); + + KeyStore keyStore = recovever.create(null, entry, broker); + assertNotNull("Key store configured object is not created", keyStore); + assertEquals(id, keyStore.getId()); + + //verify we can retrieve the actual password using the method + assertEquals(TestSSLConstants.BROKER_TRUSTSTORE_PASSWORD, keyStore.getPassword()); + assertNotNull(keyStore.getPassword()); + + //verify that we havent configured the key store with the actual dummy password value + assertFalse(AbstractKeyStoreAdapter.DUMMY_PASSWORD_MASK.equals(keyStore.getPassword())); + + // Verify the remaining attributes, including that the password value returned + // via getAttribute is actually the dummy value and not the real password + attributesCopy.put(KeyStore.PASSWORD, AbstractKeyStoreAdapter.DUMMY_PASSWORD_MASK); + for (Map.Entry attribute : attributesCopy.entrySet()) + { + Object attributeValue = keyStore.getAttribute(attribute.getKey()); + assertEquals("Unexpected value of attribute '" + attribute.getKey() + "'", attribute.getValue(), attributeValue); + } + } + + public void testCreateWithMissedRequiredAttributes() + { + Map attributes = getKeyStoreAttributes(); + + UUID id = UUID.randomUUID(); + Broker broker = mock(Broker.class); + ConfigurationEntry entry = mock(ConfigurationEntry.class); + when(entry.getId()).thenReturn(id); + + KeyStoreRecoverer recovever = new KeyStoreRecoverer(); + + String[] mandatoryProperties = {KeyStore.NAME, KeyStore.PATH, KeyStore.PASSWORD}; + for (int i = 0; i < mandatoryProperties.length; i++) + { + Map properties = new HashMap(attributes); + properties.remove(mandatoryProperties[i]); + when(entry.getAttributes()).thenReturn(properties); + try + { + recovever.create(null, entry, broker); + fail("Cannot create key store without a " + mandatoryProperties[i]); + } + catch(IllegalArgumentException e) + { + // pass + } + } + } + + private Map getKeyStoreAttributes() + { + Map attributes = new HashMap(); + attributes.put(KeyStore.NAME, getName()); + attributes.put(KeyStore.PATH, TestSSLConstants.BROKER_KEYSTORE); + attributes.put(KeyStore.PASSWORD, TestSSLConstants.BROKER_KEYSTORE_PASSWORD); + attributes.put(KeyStore.TYPE, "jks"); + attributes.put(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM, KeyManagerFactory.getDefaultAlgorithm()); + attributes.put(KeyStore.CERTIFICATE_ALIAS, "java-broker"); + return attributes; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/PluginRecovererTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/PluginRecovererTest.java new file mode 100644 index 0000000000..42fd742407 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/PluginRecovererTest.java @@ -0,0 +1,117 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.startup; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import junit.framework.TestCase; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Plugin; +import org.apache.qpid.server.plugin.PluginFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; + +public class PluginRecovererTest extends TestCase +{ + private UUID _id; + private Map _attributes; + + private PluginFactory _factory; + private QpidServiceLoader _pluginFactoryServiceLoader; + private Broker _broker; + private ConfigurationEntry _configurationEntry; + + @SuppressWarnings("unchecked") + protected void setUp() throws Exception + { + super.setUp(); + _id = UUID.randomUUID(); + _attributes = new HashMap(); + + _factory = mock(PluginFactory.class); + + _pluginFactoryServiceLoader = mock(QpidServiceLoader.class); + when(_pluginFactoryServiceLoader.instancesOf(PluginFactory.class)).thenReturn(Collections.singletonList(_factory )); + + _broker = mock(Broker.class); + + _configurationEntry = mock(ConfigurationEntry.class); + when(_configurationEntry.getId()).thenReturn(_id); + when(_configurationEntry.getAttributes()).thenReturn(_attributes); + } + + public void testCreate() + { + Plugin pluginFromFactory = mock(Plugin.class); + when(pluginFromFactory.getId()).thenReturn(_id); + when(_factory.createInstance(_id, _attributes, _broker)).thenReturn(pluginFromFactory); + + PluginRecoverer pluginRecoverer = new PluginRecoverer(_pluginFactoryServiceLoader); + ConfiguredObject pluginFromRecoverer = pluginRecoverer.create(null, _configurationEntry, _broker); + assertNotNull("Null group provider", pluginFromRecoverer); + assertSame("Unexpected plugin", pluginFromFactory, pluginFromRecoverer); + assertEquals("Unexpected ID", _id, pluginFromRecoverer.getId()); + } + + public void testCreateThrowsExceptionForUnexpectedId() + { + Plugin pluginFromFactory = mock(Plugin.class); + when(pluginFromFactory.getId()).thenReturn(UUID.randomUUID()); + when(_factory.createInstance(_id, _attributes, _broker)).thenReturn(pluginFromFactory); + + PluginRecoverer pluginRecoverer = new PluginRecoverer(_pluginFactoryServiceLoader); + try + { + pluginRecoverer.create(null, _configurationEntry, _broker); + fail("An exception should be thrown for incorrect id"); + } + catch(IllegalStateException e) + { + //pass + } + } + + public void testCreateThrowsExceptionWhenNoPluginIsCreated() + { + when(_factory.createInstance(_id, _attributes, _broker)).thenReturn(null); + + PluginRecoverer pluginRecoverer = new PluginRecoverer(_pluginFactoryServiceLoader); + try + { + pluginRecoverer.create(null, _configurationEntry, _broker); + fail("Configuration exception should be thrown when plugin is not created"); + } + catch(IllegalConfigurationException e) + { + // pass + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/PreferencesProviderRecovererTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/PreferencesProviderRecovererTest.java new file mode 100644 index 0000000000..8c316b5f53 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/PreferencesProviderRecovererTest.java @@ -0,0 +1,96 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.configuration.startup; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.model.adapter.FileSystemPreferencesProvider; +import org.apache.qpid.server.model.adapter.PreferencesProviderCreator; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestFileUtils; + +public class PreferencesProviderRecovererTest extends QpidTestCase +{ + private AuthenticationProvider _authenticationProvider; + private Broker _broker; + + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _authenticationProvider = mock(AuthenticationProvider.class); + _broker = BrokerTestHelper.createBrokerMock(); + when(_authenticationProvider.getParent(Broker.class)).thenReturn(_broker); + } + + public void tearDown() throws Exception + { + try + { + BrokerTestHelper.tearDown(); + } + finally + { + super.tearDown(); + } + } + + public void testRecoverFileSystemPreferencesProvider() + { + PreferencesProviderRecoverer recoverer = new PreferencesProviderRecoverer(new PreferencesProviderCreator()); + + Map attributes = new HashMap(); + UUID id = UUID.randomUUID(); + attributes.put(PreferencesProvider.TYPE, FileSystemPreferencesProvider.class); + attributes.put(PreferencesProvider.NAME, "test-provider"); + File file = TestFileUtils.createTempFile(this, ".prefs.json", "{\"test_user\":{\"pref1\": \"pref1Value\", \"pref2\": 1.0} }"); + try + { + attributes.put(FileSystemPreferencesProvider.PATH, file.getAbsolutePath()); + ConfigurationEntry entry = new ConfigurationEntry(id, PreferencesProvider.class.getSimpleName(), attributes, Collections.emptySet(), mock(ConfigurationEntryStore.class)); + PreferencesProvider provider = recoverer.create(mock(RecovererProvider.class), entry, _authenticationProvider); + assertNotNull("Preferences provider was not recovered", provider); + assertEquals("Unexpected name", "test-provider", provider.getName()); + assertEquals("Unexpected id", id, provider.getId()); + assertEquals("Unexpected path", file.getAbsolutePath(), provider.getAttribute(FileSystemPreferencesProvider.PATH)); + } + finally + { + file.delete(); + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/TrustStoreRecovererTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/TrustStoreRecovererTest.java new file mode 100644 index 0000000000..4d92f99306 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/TrustStoreRecovererTest.java @@ -0,0 +1,117 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.startup; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.net.ssl.TrustManagerFactory; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.model.adapter.AbstractKeyStoreAdapter; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestSSLConstants; + +public class TrustStoreRecovererTest extends QpidTestCase +{ + public void testCreateWithAllAttributesProvided() + { + Map attributes = getTrustStoreAttributes(); + Map attributesCopy = new HashMap(attributes); + + UUID id = UUID.randomUUID(); + Broker broker = mock(Broker.class); + ConfigurationEntry entry = mock(ConfigurationEntry.class); + when(entry.getAttributes()).thenReturn(attributes); + when(entry.getId()).thenReturn(id); + + TrustStoreRecoverer recoverer = new TrustStoreRecoverer(); + + TrustStore trustStore = recoverer.create(null, entry, broker); + assertNotNull("Trust store configured object is not created", trustStore); + assertEquals(id, trustStore.getId()); + + //verify we can retrieve the actual password using the method + assertEquals(TestSSLConstants.BROKER_TRUSTSTORE_PASSWORD, trustStore.getPassword()); + assertNotNull(trustStore.getPassword()); + + //verify that we havent configured the trust store with the actual dummy password value + assertFalse(AbstractKeyStoreAdapter.DUMMY_PASSWORD_MASK.equals(trustStore.getPassword())); + + // Verify the remaining attributes, including that the password value returned + // via getAttribute is actually the dummy value and not the real password + attributesCopy.put(TrustStore.PASSWORD, AbstractKeyStoreAdapter.DUMMY_PASSWORD_MASK); + for (Map.Entry attribute : attributesCopy.entrySet()) + { + Object attributeValue = trustStore.getAttribute(attribute.getKey()); + assertEquals("Unexpected value of attribute '" + attribute.getKey() + "'", attribute.getValue(), attributeValue); + } + } + + public void testCreateWithMissedRequiredAttributes() + { + Map attributes = getTrustStoreAttributes(); + + UUID id = UUID.randomUUID(); + Broker broker = mock(Broker.class); + ConfigurationEntry entry = mock(ConfigurationEntry.class); + when(entry.getAttributes()).thenReturn(attributes); + when(entry.getId()).thenReturn(id); + + TrustStoreRecoverer recovever = new TrustStoreRecoverer(); + + String[] mandatoryProperties = {TrustStore.NAME, TrustStore.PATH, TrustStore.PASSWORD}; + for (int i = 0; i < mandatoryProperties.length; i++) + { + Map properties = new HashMap(attributes); + properties.remove(mandatoryProperties[i]); + when(entry.getAttributes()).thenReturn(properties); + try + { + recovever.create(null, entry, broker); + fail("Cannot create key store without a " + mandatoryProperties[i]); + } + catch(IllegalArgumentException e) + { + // pass + } + } + } + + private Map getTrustStoreAttributes() + { + Map attributes = new HashMap(); + attributes.put(TrustStore.NAME, getName()); + attributes.put(TrustStore.PATH, TestSSLConstants.BROKER_TRUSTSTORE); + attributes.put(TrustStore.PASSWORD, TestSSLConstants.BROKER_TRUSTSTORE_PASSWORD); + attributes.put(TrustStore.TYPE, "jks"); + attributes.put(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM, TrustManagerFactory.getDefaultAlgorithm()); + attributes.put(TrustStore.PEERS_ONLY, Boolean.TRUE); + return attributes; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/VirtualHostRecovererTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/VirtualHostRecovererTest.java new file mode 100644 index 0000000000..f00d12b77d --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/startup/VirtualHostRecovererTest.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.configuration.startup; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.virtualhost.StandardVirtualHostFactory; +import org.apache.qpid.test.utils.TestFileUtils; + +public class VirtualHostRecovererTest extends TestCase +{ + public void testCreate() + { + StatisticsGatherer statisticsGatherer = mock(StatisticsGatherer.class); + SecurityManager securityManager = mock(SecurityManager.class); + ConfigurationEntry entry = mock(ConfigurationEntry.class); + Broker parent = mock(Broker.class); + when(parent.getAttribute(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD)).thenReturn(3000l); + when(parent.getSecurityManager()).thenReturn(securityManager); + + VirtualHostRecoverer recoverer = new VirtualHostRecoverer(statisticsGatherer); + Map attributes = new HashMap(); + String name = getName(); + attributes.put(VirtualHost.NAME, name); + File file = TestFileUtils.createTempFile(this, ".xml", "" + name + "<" + name + + ">"); + attributes.put(VirtualHost.CONFIG_PATH, file.getAbsolutePath()); + when(entry.getAttributes()).thenReturn(attributes); + + VirtualHost host = recoverer.create(null, entry, parent); + + assertNotNull("Null is returned", host); + assertEquals("Unexpected name", getName(), host.getName()); + } + + public void testCreateVirtualHostFromStoreConfigAtrributes() + { + StatisticsGatherer statisticsGatherer = mock(StatisticsGatherer.class); + SecurityManager securityManager = mock(SecurityManager.class); + ConfigurationEntry entry = mock(ConfigurationEntry.class); + Broker parent = mock(Broker.class); + when(parent.getAttribute(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD)).thenReturn(3000l); + when(parent.getSecurityManager()).thenReturn(securityManager); + + VirtualHostRecoverer recoverer = new VirtualHostRecoverer(statisticsGatherer); + Map attributes = new HashMap(); + attributes.put(VirtualHost.NAME, getName()); + attributes.put(VirtualHost.TYPE, StandardVirtualHostFactory.TYPE); + + attributes.put(VirtualHost.STORE_TYPE, "TESTMEMORY"); + when(entry.getAttributes()).thenReturn(attributes); + + VirtualHost host = recoverer.create(null, entry, parent); + + assertNotNull("Null is returned", host); + assertEquals("Unexpected name", getName(), host.getName()); + } + + public void testCreateWithoutMandatoryAttributesResultsInException() + { + Map attributes = new HashMap(); + attributes.put(VirtualHost.NAME, getName()); + attributes.put(VirtualHost.CONFIG_PATH, "/path/to/virtualhost.xml"); + String[] mandatoryAttributes = {VirtualHost.NAME, VirtualHost.CONFIG_PATH}; + + checkMandatoryAttributesAreValidated(mandatoryAttributes, attributes); + + attributes = new HashMap(); + attributes.put(VirtualHost.NAME, getName()); + attributes.put(VirtualHost.STORE_TYPE, "MEMORY"); + mandatoryAttributes = new String[]{VirtualHost.NAME, VirtualHost.STORE_TYPE}; + + checkMandatoryAttributesAreValidated(mandatoryAttributes, attributes); + } + + public void checkMandatoryAttributesAreValidated(String[] mandatoryAttributes, Map attributes) + { + StatisticsGatherer statisticsGatherer = mock(StatisticsGatherer.class); + SecurityManager securityManager = mock(SecurityManager.class); + ConfigurationEntry entry = mock(ConfigurationEntry.class); + Broker parent = mock(Broker.class); + when(parent.getSecurityManager()).thenReturn(securityManager); + VirtualHostRecoverer recoverer = new VirtualHostRecoverer(statisticsGatherer); + + for (String name : mandatoryAttributes) + { + Map copy = new HashMap(attributes); + copy.remove(name); + when(entry.getAttributes()).thenReturn(copy); + try + { + recoverer.create(null, entry, parent); + fail("Cannot create a virtual host without a manadatory attribute " + name); + } + catch(IllegalConfigurationException e) + { + // pass + } + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java new file mode 100644 index 0000000000..d56481340b --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ConfigurationEntryStoreTestCase.java @@ -0,0 +1,391 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.store; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManager; +import org.apache.qpid.server.security.auth.manager.ExternalAuthenticationManager; +import org.apache.qpid.test.utils.QpidTestCase; + +public abstract class ConfigurationEntryStoreTestCase extends QpidTestCase +{ + private ConfigurationEntryStore _store; + + private UUID _brokerId; + private UUID _virtualHostId; + private UUID _authenticationProviderId; + + private Map _brokerAttributes; + private Map _virtualHostAttributes; + private Map _authenticationProviderAttributes; + + public void setUp() throws Exception + { + super.setUp(); + + _brokerId = UUID.randomUUID(); + _brokerAttributes = new HashMap(); + _brokerAttributes.put(Broker.DEFAULT_VIRTUAL_HOST, "test"); + _brokerAttributes.put(Broker.QUEUE_ALERT_THRESHOLD_MESSAGE_AGE, 9); + _brokerAttributes.put(Broker.QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, 8); + _brokerAttributes.put(Broker.QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, 7); + _brokerAttributes.put(Broker.QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE, 6); + _brokerAttributes.put(Broker.QUEUE_ALERT_REPEAT_GAP, 5); + _brokerAttributes.put(Broker.QUEUE_FLOW_CONTROL_SIZE_BYTES, 5); + _brokerAttributes.put(Broker.QUEUE_FLOW_CONTROL_RESUME_SIZE_BYTES, 3); + _brokerAttributes.put(Broker.QUEUE_MAXIMUM_DELIVERY_ATTEMPTS, 2); + _brokerAttributes.put(Broker.QUEUE_DEAD_LETTER_QUEUE_ENABLED, true); + _brokerAttributes.put(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD, 1); + _brokerAttributes.put(Broker.CONNECTION_SESSION_COUNT_LIMIT, 1000); + _brokerAttributes.put(Broker.CONNECTION_HEART_BEAT_DELAY, 2000); + _brokerAttributes.put(Broker.STATISTICS_REPORTING_PERIOD, 4000); + _brokerAttributes.put(Broker.STATISTICS_REPORTING_RESET_ENABLED, true); + + _virtualHostId = UUID.randomUUID(); + _virtualHostAttributes = new HashMap(); + _virtualHostAttributes.put(VirtualHost.NAME, "test"); + _virtualHostAttributes.put(VirtualHost.CONFIG_PATH, "/path/to/phantom/test"); + + _authenticationProviderId = UUID.randomUUID(); + _authenticationProviderAttributes = new HashMap(); + _authenticationProviderAttributes.put(AuthenticationProvider.NAME, "authenticationProvider1"); + _authenticationProviderAttributes.put(AuthenticationManagerFactory.ATTRIBUTE_TYPE, AnonymousAuthenticationManager.class.getSimpleName()); + + _store = createStore(_brokerId, _brokerAttributes); + addConfiguration(_virtualHostId, VirtualHost.class.getSimpleName(), _virtualHostAttributes); + addConfiguration(_authenticationProviderId, AuthenticationProvider.class.getSimpleName(), _authenticationProviderAttributes); + } + + // ??? perhaps it should not be abstract + protected abstract ConfigurationEntryStore createStore(UUID brokerId, Map brokerAttributes) throws Exception; + + protected abstract void addConfiguration(UUID id, String type, Map attributes); + + protected ConfigurationEntryStore getStore() + { + return _store; + } + + public void testGetRootEntry() + { + ConfigurationEntry brokerConfigEntry = _store.getRootEntry(); + assertNotNull("Root entry does not exist", brokerConfigEntry); + assertEquals("Unexpected id", _brokerId, brokerConfigEntry.getId()); + assertEquals("Unexpected type ", Broker.class.getSimpleName(), brokerConfigEntry.getType()); + Map attributes = brokerConfigEntry.getAttributes(); + assertNotNull("Attributes cannot be null", attributes); + for (Map.Entry attribute : _brokerAttributes.entrySet()) + { + assertEquals("Unexpected attribute " + attribute.getKey(), attribute.getValue(), attributes.get(attribute.getKey())); + } + } + + public void testGetEntry() + { + ConfigurationEntry authenticationProviderConfigEntry = _store.getEntry(_authenticationProviderId); + assertNotNull("Provider with id " + _authenticationProviderId + " should exist", authenticationProviderConfigEntry); + assertEquals("Unexpected id", _authenticationProviderId, authenticationProviderConfigEntry.getId()); + assertEquals("Unexpected type ", AuthenticationProvider.class.getSimpleName(), authenticationProviderConfigEntry.getType()); + Map attributes = authenticationProviderConfigEntry.getAttributes(); + assertNotNull("Attributes cannot be null", attributes); + assertEquals("Unexpected attributes", _authenticationProviderAttributes, attributes); + } + + public void testRemove() + { + Map virtualHostAttributes = new HashMap(); + virtualHostAttributes.put(VirtualHost.NAME, getName()); + virtualHostAttributes.put(VirtualHost.CONFIG_PATH, "/path/to/phantom/virtualhost/config"); + UUID virtualHostId = UUID.randomUUID(); + addConfiguration(virtualHostId, VirtualHost.class.getSimpleName(), virtualHostAttributes); + + assertNotNull("Virtual host with id " + virtualHostId + " should exist", _store.getEntry(virtualHostId)); + + _store.remove(virtualHostId); + assertNull("Authentication provider configuration should be removed", _store.getEntry(virtualHostId)); + } + + public void testRemoveMultipleEntries() + { + Map virtualHost1Attributes = new HashMap(); + virtualHost1Attributes.put(VirtualHost.NAME, "test1"); + virtualHost1Attributes.put(VirtualHost.CONFIG_PATH, "/path/to/phantom/virtualhost/config1"); + UUID virtualHost1Id = UUID.randomUUID(); + addConfiguration(virtualHost1Id, VirtualHost.class.getSimpleName(), virtualHost1Attributes); + + Map virtualHost2Attributes = new HashMap(); + virtualHost2Attributes.put(VirtualHost.NAME, "test1"); + virtualHost2Attributes.put(VirtualHost.CONFIG_PATH, "/path/to/phantom/virtualhost/config2"); + UUID virtualHost2Id = UUID.randomUUID(); + addConfiguration(virtualHost2Id, VirtualHost.class.getSimpleName(), virtualHost2Attributes); + + assertNotNull("Virtual host with id " + virtualHost1Id + " should exist", _store.getEntry(virtualHost1Id)); + assertNotNull("Virtual host with id " + virtualHost2Id + " should exist", _store.getEntry(virtualHost2Id)); + + UUID[] deletedIds = _store.remove(virtualHost1Id, virtualHost2Id); + assertNotNull("Unexpected deleted ids", deletedIds); + assertEquals("Unexpected id of first deleted virtual host", virtualHost1Id , deletedIds[0]); + assertEquals("Unexpected id of second deleted virtual host", virtualHost2Id , deletedIds[1]); + assertNull("First virtual host configuration should be removed", _store.getEntry(virtualHost1Id)); + assertNull("Second virtual host configuration should be removed", _store.getEntry(virtualHost2Id)); + } + + public void testSaveBroker() + { + ConfigurationEntry brokerConfigEntry = _store.getRootEntry(); + Map attributes = new HashMap(); + attributes.put(Broker.DEFAULT_VIRTUAL_HOST, "test"); + attributes.put(Broker.QUEUE_ALERT_THRESHOLD_MESSAGE_AGE, 19); + attributes.put(Broker.QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_MESSAGES, 18); + attributes.put(Broker.QUEUE_ALERT_THRESHOLD_QUEUE_DEPTH_BYTES, 17); + attributes.put(Broker.QUEUE_ALERT_THRESHOLD_MESSAGE_SIZE, 16); + attributes.put(Broker.QUEUE_ALERT_REPEAT_GAP, 15); + attributes.put(Broker.QUEUE_FLOW_CONTROL_SIZE_BYTES, 15); + attributes.put(Broker.QUEUE_FLOW_CONTROL_RESUME_SIZE_BYTES, 13); + attributes.put(Broker.QUEUE_MAXIMUM_DELIVERY_ATTEMPTS, 12); + attributes.put(Broker.QUEUE_DEAD_LETTER_QUEUE_ENABLED, false); + attributes.put(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD, 11); + attributes.put(Broker.CONNECTION_SESSION_COUNT_LIMIT, 11000); + attributes.put(Broker.CONNECTION_HEART_BEAT_DELAY, 12000); + attributes.put(Broker.STATISTICS_REPORTING_PERIOD, 14000); + attributes.put(Broker.STATISTICS_REPORTING_RESET_ENABLED, false); + ConfigurationEntry updatedBrokerEntry = new ConfigurationEntry(_brokerId, Broker.class.getSimpleName(), attributes, + brokerConfigEntry.getChildrenIds(), _store); + + _store.save(updatedBrokerEntry); + + ConfigurationEntry newBrokerConfigEntry = _store.getRootEntry(); + assertNotNull("Root entry does not exist", newBrokerConfigEntry); + assertEquals("Unexpected id", _brokerId, newBrokerConfigEntry.getId()); + assertEquals("Unexpected type ", Broker.class.getSimpleName(), newBrokerConfigEntry.getType()); + Map newBrokerattributes = newBrokerConfigEntry.getAttributes(); + assertNotNull("Attributes cannot be null", newBrokerattributes); + assertEquals("Unexpected attributes", attributes, newBrokerattributes); + } + + public void testSaveNewVirtualHost() + { + Map virtualHostAttributes = new HashMap(); + virtualHostAttributes.put(VirtualHost.NAME, "test1"); + virtualHostAttributes.put(VirtualHost.CONFIG_PATH, "/path/to/phantom/virtualhost/config1"); + UUID virtualHostId = UUID.randomUUID(); + ConfigurationEntry hostEntry = new ConfigurationEntry(virtualHostId, VirtualHost.class.getSimpleName(), virtualHostAttributes, + Collections. emptySet(), _store); + + _store.save(hostEntry); + + ConfigurationEntry configurationEntry = _store.getEntry(virtualHostId); + assertEquals("Unexpected virtual host configuration", hostEntry, configurationEntry); + assertEquals("Unexpected type", VirtualHost.class.getSimpleName(), configurationEntry.getType()); + assertEquals("Unexpected virtual host attributes", hostEntry.getAttributes(), configurationEntry.getAttributes()); + assertTrue("Unexpected virtual host children found", hostEntry.getChildrenIds().isEmpty()); + } + + public void testSaveExistingVirtualHost() + { + ConfigurationEntry hostEntry = _store.getEntry(_virtualHostId); + assertNotNull("Host configuration is not found", hostEntry); + + Map virtualHostAttributes = new HashMap(); + virtualHostAttributes.put(VirtualHost.NAME, "test"); + virtualHostAttributes.put(VirtualHost.CONFIG_PATH, "/path/to/new/phantom/test/configuration"); + + ConfigurationEntry updatedEntry = new ConfigurationEntry(_virtualHostId, VirtualHost.class.getSimpleName(), virtualHostAttributes, + hostEntry.getChildrenIds(), _store); + _store.save(updatedEntry); + + ConfigurationEntry newHostEntry = _store.getEntry(_virtualHostId); + assertEquals("Unexpected virtual host configuration", updatedEntry, newHostEntry); + assertEquals("Unexpected type", VirtualHost.class.getSimpleName(), newHostEntry.getType()); + assertEquals("Unexpected virtual host attributes", updatedEntry.getAttributes(), newHostEntry.getAttributes()); + assertEquals("Unexpected virtual host children found", updatedEntry.getChildrenIds(), newHostEntry.getChildrenIds()); + } + + public void testSaveNewAuthenticationProvider() + { + UUID authenticationProviderId = UUID.randomUUID(); + Map authenticationProviderAttributes = new HashMap(); + authenticationProviderAttributes.put(AuthenticationProvider.NAME, "authenticationProvider1"); + authenticationProviderAttributes.put(AuthenticationManagerFactory.ATTRIBUTE_TYPE, ExternalAuthenticationManager.class.getSimpleName()); + ConfigurationEntry providerEntry = new ConfigurationEntry(authenticationProviderId, AuthenticationProvider.class.getSimpleName(), + authenticationProviderAttributes, Collections. emptySet(), _store); + + _store.save(providerEntry); + + ConfigurationEntry storeEntry = _store.getEntry(authenticationProviderId); + assertEquals("Unexpected provider configuration", providerEntry, storeEntry); + assertEquals("Unexpected type", AuthenticationProvider.class.getSimpleName(), storeEntry.getType()); + assertEquals("Unexpected provider attributes", providerEntry.getAttributes(), storeEntry.getAttributes()); + assertTrue("Unexpected provider children found", storeEntry.getChildrenIds().isEmpty()); + } + + public void testSaveExistingAuthenticationProvider() + { + ConfigurationEntry providerEntry = _store.getEntry(_authenticationProviderId); + assertNotNull("provider configuration is not found", providerEntry); + + Map authenticationProviderAttributes = new HashMap(); + authenticationProviderAttributes.put(AuthenticationProvider.NAME, "authenticationProvider1"); + authenticationProviderAttributes.put(AuthenticationManagerFactory.ATTRIBUTE_TYPE, ExternalAuthenticationManager.class.getSimpleName()); + ConfigurationEntry updatedEntry = new ConfigurationEntry(_authenticationProviderId, AuthenticationProvider.class.getSimpleName(), + authenticationProviderAttributes, Collections. emptySet(), _store); + _store.save(updatedEntry); + + ConfigurationEntry storeEntry = _store.getEntry(_authenticationProviderId); + assertEquals("Unexpected provider configuration", updatedEntry, storeEntry); + assertEquals("Unexpected type", AuthenticationProvider.class.getSimpleName(), storeEntry.getType()); + assertEquals("Unexpected provider attributes", updatedEntry.getAttributes(), storeEntry.getAttributes()); + assertTrue("Unexpected provider children found", storeEntry.getChildrenIds().isEmpty()); + } + + public void testSaveTrustStore() + { + UUID trustStoreId = UUID.randomUUID(); + Map attributes = new HashMap(); + attributes.put(TrustStore.NAME, getName()); + attributes.put(TrustStore.PATH, "/path/to/truststore"); + attributes.put(TrustStore.PASSWORD, "my-secret-password"); + attributes.put(TrustStore.TYPE, "NON-JKS"); + attributes.put(TrustStore.TRUST_MANAGER_FACTORY_ALGORITHM, "NON-STANDARD"); + attributes.put(TrustStore.DESCRIPTION, "Description"); + + ConfigurationEntry trustStoreEntry = new ConfigurationEntry(trustStoreId, TrustStore.class.getSimpleName(), attributes, + Collections. emptySet(), _store); + + _store.save(trustStoreEntry); + + ConfigurationEntry storeEntry = _store.getEntry(trustStoreId); + assertEquals("Unexpected trust store configuration", trustStoreEntry, storeEntry); + assertEquals("Unexpected type", TrustStore.class.getSimpleName(), storeEntry.getType()); + assertEquals("Unexpected provider attributes", trustStoreEntry.getAttributes(), storeEntry.getAttributes()); + assertTrue("Unexpected provider children found", storeEntry.getChildrenIds().isEmpty()); + } + + public void testSaveKeyStore() + { + UUID keyStoreId = UUID.randomUUID(); + Map attributes = new HashMap(); + attributes.put(KeyStore.NAME, getName()); + attributes.put(KeyStore.PATH, "/path/to/truststore"); + attributes.put(KeyStore.PASSWORD, "my-secret-password"); + attributes.put(KeyStore.TYPE, "NON-JKS"); + attributes.put(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM, "NON-STANDARD"); + attributes.put(KeyStore.DESCRIPTION, "Description"); + attributes.put(KeyStore.CERTIFICATE_ALIAS, "Alias"); + + ConfigurationEntry keyStoreEntry = new ConfigurationEntry(keyStoreId, KeyStore.class.getSimpleName(), attributes, Collections. emptySet(), + _store); + + _store.save(keyStoreEntry); + + ConfigurationEntry storeEntry = _store.getEntry(keyStoreId); + assertEquals("Unexpected key store configuration", keyStoreEntry, storeEntry); + assertEquals("Unexpected type", KeyStore.class.getSimpleName(), storeEntry.getType()); + assertEquals("Unexpected provider attributes", keyStoreEntry.getAttributes(), storeEntry.getAttributes()); + assertTrue("Unexpected provider children found", storeEntry.getChildrenIds().isEmpty()); + } + + public void testSaveGroupProvider() + { + UUID groupProviderId = UUID.randomUUID(); + Map attributes = new HashMap(); + attributes.put(GroupProvider.NAME, getName()); + + ConfigurationEntry groupProviderEntry = new ConfigurationEntry(groupProviderId, GroupProvider.class.getSimpleName(), attributes, + Collections. emptySet(), _store); + + _store.save(groupProviderEntry); + + ConfigurationEntry storeEntry = _store.getEntry(groupProviderId); + assertEquals("Unexpected group provider configuration", groupProviderEntry, storeEntry); + assertEquals("Unexpected type", GroupProvider.class.getSimpleName(), storeEntry.getType()); + assertEquals("Unexpected group provider attributes", groupProviderEntry.getAttributes(), storeEntry.getAttributes()); + assertTrue("Unexpected provider children found", storeEntry.getChildrenIds().isEmpty()); + } + + public void testSavePort() + { + UUID portId = UUID.randomUUID(); + Map attributes = new HashMap(); + Set tcpTransportSet = Collections.singleton(Transport.TCP.name()); + attributes.put(Port.PORT, 9999); + attributes.put(Port.TRANSPORTS, tcpTransportSet); + attributes.put(Port.TCP_NO_DELAY, true); + attributes.put(Port.RECEIVE_BUFFER_SIZE, 1); + attributes.put(Port.SEND_BUFFER_SIZE, 2); + attributes.put(Port.NEED_CLIENT_AUTH, true); + attributes.put(Port.WANT_CLIENT_AUTH, true); + + ConfigurationEntry portEntry = new ConfigurationEntry(portId, Port.class.getSimpleName(), attributes, Collections. emptySet(), _store); + + _store.save(portEntry); + + ConfigurationEntry storeEntry = _store.getEntry(portId); + assertEquals("Unexpected port configuration", portEntry, storeEntry); + assertEquals("Unexpected type", Port.class.getSimpleName(), storeEntry.getType()); + assertEquals("Unexpected port attributes", portEntry.getAttributes(), storeEntry.getAttributes()); + assertTrue("Unexpected port children found", storeEntry.getChildrenIds().isEmpty()); + } + + public void testMultipleSave() + { + UUID virtualHostId = UUID.randomUUID(); + Map virtualHostAttributes = new HashMap(); + virtualHostAttributes.put(VirtualHost.NAME, "test1"); + virtualHostAttributes.put(VirtualHost.CONFIG_PATH, "/path/to/phantom/virtualhost/config1"); + ConfigurationEntry hostEntry = new ConfigurationEntry(virtualHostId, VirtualHost.class.getSimpleName(), virtualHostAttributes, + Collections. emptySet(), _store); + + UUID keyStoreId = UUID.randomUUID(); + Map attributes = new HashMap(); + attributes.put(KeyStore.NAME, getName()); + attributes.put(KeyStore.PATH, "/path/to/truststore"); + attributes.put(KeyStore.PASSWORD, "my-secret-password"); + attributes.put(KeyStore.TYPE, "NON-JKS"); + attributes.put(KeyStore.KEY_MANAGER_FACTORY_ALGORITHM, "NON-STANDARD"); + attributes.put(KeyStore.DESCRIPTION, "Description"); + attributes.put(KeyStore.CERTIFICATE_ALIAS, "Alias"); + + ConfigurationEntry keyStoreEntry = new ConfigurationEntry(keyStoreId, KeyStore.class.getSimpleName(), attributes, Collections. emptySet(), + _store); + + _store.save(hostEntry, keyStoreEntry); + + assertNotNull("Virtual host is not found", _store.getEntry(virtualHostId)); + assertNotNull("Key store is not found", _store.getEntry(keyStoreId)); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.java new file mode 100644 index 0000000000..3412543030 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/JsonConfigurationEntryStoreTest.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.configuration.store; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.test.utils.TestFileUtils; +import org.codehaus.jackson.JsonGenerationException; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig; + +public class JsonConfigurationEntryStoreTest extends ConfigurationEntryStoreTestCase +{ + private File _storeFile; + private ObjectMapper _objectMapper; + + @Override + public void setUp() throws Exception + { + _objectMapper = new ObjectMapper(); + _objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true); + super.setUp(); + } + + @Override + public void tearDown() throws Exception + { + _storeFile.delete(); + super.tearDown(); + } + + @Override + protected ConfigurationEntryStore createStore(UUID brokerId, Map brokerAttributes) throws Exception + { + _storeFile = createStoreFile(brokerId, brokerAttributes); + JsonConfigurationEntryStore store = new JsonConfigurationEntryStore(_storeFile.getAbsolutePath(), null, false, Collections.emptyMap()); + return store; + } + + private File createStoreFile(UUID brokerId, Map brokerAttributes) throws IOException, + JsonGenerationException, JsonMappingException + { + return createStoreFile(brokerId, brokerAttributes, true); + } + + private File createStoreFile(UUID brokerId, Map brokerAttributes, boolean setVersion) throws IOException, + JsonGenerationException, JsonMappingException + { + Map brokerObjectMap = new HashMap(); + brokerObjectMap.put(Broker.ID, brokerId); + if (setVersion) + { + brokerObjectMap.put(Broker.STORE_VERSION, MemoryConfigurationEntryStore.STORE_VERSION); + } + brokerObjectMap.put(Broker.NAME, getTestName()); + brokerObjectMap.putAll(brokerAttributes); + + StringWriter sw = new StringWriter(); + _objectMapper.writeValue(sw, brokerObjectMap); + + String brokerJson = sw.toString(); + + return TestFileUtils.createTempFile(this, ".json", brokerJson); + } + + @Override + protected void addConfiguration(UUID id, String type, Map attributes) + { + ConfigurationEntryStore store = getStore(); + store.save(new ConfigurationEntry(id, type, attributes, Collections. emptySet(), store)); + } + + public void testAttributeIsResolvedFromSystemProperties() + { + String defaultVhost = getTestName(); + setTestSystemProperty("my.test.property", defaultVhost); + + ConfigurationEntryStore store = getStore(); + ConfigurationEntry brokerConfigEntry = store.getRootEntry(); + Map attributes = new HashMap(brokerConfigEntry.getAttributes()); + attributes.put(Broker.DEFAULT_VIRTUAL_HOST, "${my.test.property}"); + ConfigurationEntry updatedBrokerEntry = new ConfigurationEntry(brokerConfigEntry.getId(), Broker.class.getSimpleName(), + attributes, brokerConfigEntry.getChildrenIds(), store); + store.save(updatedBrokerEntry); + + JsonConfigurationEntryStore store2 = new JsonConfigurationEntryStore(_storeFile.getAbsolutePath(), null, false, Collections.emptyMap()); + + assertEquals("Unresolved default virtualhost value", defaultVhost, store2.getRootEntry().getAttributes().get(Broker.DEFAULT_VIRTUAL_HOST)); + } + + public void testCreateEmptyStore() + { + File file = TestFileUtils.createTempFile(this, ".json"); + try + { + new JsonConfigurationEntryStore(file.getAbsolutePath(), null, false, Collections.emptyMap()); + fail("Cannot create a new store without initial store"); + } + catch(IllegalConfigurationException e) + { + // pass + } + } + + public void testCreateFromExistingLocation() throws Exception + { + UUID brokerId = UUID.randomUUID(); + Map brokerAttributes = new HashMap(); + brokerAttributes.put(Broker.NAME, getTestName()); + File file = createStoreFile(brokerId, brokerAttributes); + + JsonConfigurationEntryStore store = new JsonConfigurationEntryStore(file.getAbsolutePath(), null, false, Collections.emptyMap()); + ConfigurationEntry root = store.getRootEntry(); + assertNotNull("Root entry is not found", root); + assertEquals("Unexpected root entry", brokerId, root.getId()); + Map attributes = root.getAttributes(); + assertNotNull("Attributes not found", attributes); + assertEquals("Unexpected number of attriburtes", 2, attributes.size()); + assertEquals("Unexpected name attribute", getTestName(), attributes.get(Broker.NAME)); + assertEquals("Unexpected version attribute", 1, attributes.get(Broker.STORE_VERSION)); + } + + public void testCreateFromInitialStore() throws Exception + { + UUID brokerId = UUID.randomUUID(); + Map brokerAttributes = new HashMap(); + File initialStoreFile = createStoreFile(brokerId, brokerAttributes); + + JsonConfigurationEntryStore initialStore = new JsonConfigurationEntryStore(initialStoreFile.getAbsolutePath(), null, false, Collections.emptyMap()); + + File storeFile = TestFileUtils.createTempFile(this, ".json"); + JsonConfigurationEntryStore store = new JsonConfigurationEntryStore(storeFile.getAbsolutePath(), initialStore, false, Collections.emptyMap()); + + ConfigurationEntry root = store.getRootEntry(); + assertNotNull("Root entry is not found", root); + assertEquals("Unexpected root entry", brokerId, root.getId()); + Map attributes = root.getAttributes(); + assertNotNull("Attributes not found", attributes); + assertEquals("Unexpected number of attriburtes", 2, attributes.size()); + assertEquals("Unexpected name attribute", getTestName(), attributes.get(Broker.NAME)); + assertEquals("Unexpected version attribute", 1, attributes.get(Broker.STORE_VERSION)); + } + + public void testGetVersion() + { + assertEquals("Unexpected version", 1, getStore().getVersion()); + } + + public void testGetType() + { + assertEquals("Unexpected type", "json", getStore().getType()); + } + + public void testUnsupportedStoreVersion() throws Exception + { + UUID brokerId = UUID.randomUUID(); + Map brokerAttributes = new HashMap(); + int[] storeVersions = {Integer.MAX_VALUE, 0}; + for (int storeVersion : storeVersions) + { + brokerAttributes.put(Broker.STORE_VERSION, storeVersion); + File storeFile = null; + try + { + storeFile = createStoreFile(brokerId, brokerAttributes); + new JsonConfigurationEntryStore(storeFile.getAbsolutePath(), null, false, Collections.emptyMap()); + fail("The store creation should fail due to unsupported store version"); + } + catch (IllegalConfigurationException e) + { + assertEquals("The data of version " + storeVersion + + " can not be loaded by store of version " + MemoryConfigurationEntryStore.STORE_VERSION, e.getMessage()); + } + finally + { + if (storeFile != null) + { + storeFile.delete(); + } + } + } + } + + public void testStoreVersionNotSpecified() throws Exception + { + UUID brokerId = UUID.randomUUID(); + Map brokerAttributes = new HashMap(); + File storeFile = null; + try + { + storeFile = createStoreFile(brokerId, brokerAttributes, false); + new JsonConfigurationEntryStore(storeFile.getAbsolutePath(), null, false, Collections.emptyMap()); + fail("The store creation should fail due to unspecified store version"); + } + catch (IllegalConfigurationException e) + { + assertEquals("Broker " + Broker.STORE_VERSION + " attribute must be specified", e.getMessage()); + } + finally + { + if (storeFile != null) + { + storeFile.delete(); + } + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandlerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandlerTest.java new file mode 100644 index 0000000000..34b4fbf1ab --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/ManagementModeStoreHandlerTest.java @@ -0,0 +1,335 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.configuration.store; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.any; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Protocol; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +public class ManagementModeStoreHandlerTest extends QpidTestCase +{ + private ManagementModeStoreHandler _handler; + private BrokerOptions _options; + private ConfigurationEntryStore _store; + private ConfigurationEntry _root; + private ConfigurationEntry _portEntry; + private UUID _rootId, _portEntryId; + + protected void setUp() throws Exception + { + super.setUp(); + _rootId = UUID.randomUUID(); + _portEntryId = UUID.randomUUID(); + _store = mock(ConfigurationEntryStore.class); + _root = mock(ConfigurationEntry.class); + _portEntry = mock(ConfigurationEntry.class); + when(_store.getRootEntry()).thenReturn(_root); + when(_root.getId()).thenReturn(_rootId); + when(_portEntry.getId()).thenReturn(_portEntryId); + when(_store.getEntry(_portEntryId)).thenReturn(_portEntry); + when(_store.getEntry(_rootId)).thenReturn(_root); + when(_root.getChildrenIds()).thenReturn(Collections.singleton(_portEntryId)); + when(_portEntry.getType()).thenReturn(Port.class.getSimpleName()); + _options = new BrokerOptions(); + _handler = new ManagementModeStoreHandler(_store, _options); + } + + public void testGetRootEntryWithEmptyOptions() + { + ConfigurationEntry root = _handler.getRootEntry(); + assertEquals("Unexpected root id", _rootId, root.getId()); + assertEquals("Unexpected children", Collections.singleton(_portEntryId), root.getChildrenIds()); + } + + public void testGetRootEntryWithHttpPortOverriden() + { + _options.setManagementModeHttpPortOverride(9090); + _handler = new ManagementModeStoreHandler(_store, _options); + ConfigurationEntry root = _handler.getRootEntry(); + assertEquals("Unexpected root id", _rootId, root.getId()); + Collection childrenIds = root.getChildrenIds(); + assertEquals("Unexpected children size", 2, childrenIds.size()); + assertTrue("Store port entry id is not found", childrenIds.contains(_portEntryId)); + } + + public void testGetRootEntryWithRmiPortOverriden() + { + _options.setManagementModeRmiPortOverride(9090); + _handler = new ManagementModeStoreHandler(_store, _options); + ConfigurationEntry root = _handler.getRootEntry(); + assertEquals("Unexpected root id", _rootId, root.getId()); + Collection childrenIds = root.getChildrenIds(); + assertEquals("Unexpected children size", 3, childrenIds.size()); + assertTrue("Store port entry id is not found", childrenIds.contains(_portEntryId)); + } + + public void testGetRootEntryWithConnectorPortOverriden() + { + _options.setManagementModeJmxPortOverride(9090); + _handler = new ManagementModeStoreHandler(_store, _options); + ConfigurationEntry root = _handler.getRootEntry(); + assertEquals("Unexpected root id", _rootId, root.getId()); + Collection childrenIds = root.getChildrenIds(); + assertEquals("Unexpected children size", 2, childrenIds.size()); + assertTrue("Store port entry id is not found", childrenIds.contains(_portEntryId)); + } + + public void testGetRootEntryWithManagementPortsOverriden() + { + _options.setManagementModeHttpPortOverride(1000); + _options.setManagementModeRmiPortOverride(2000); + _options.setManagementModeJmxPortOverride(3000); + _handler = new ManagementModeStoreHandler(_store, _options); + ConfigurationEntry root = _handler.getRootEntry(); + assertEquals("Unexpected root id", _rootId, root.getId()); + Collection childrenIds = root.getChildrenIds(); + assertEquals("Unexpected children size", 4, childrenIds.size()); + assertTrue("Store port entry id is not found", childrenIds.contains(_portEntryId)); + } + + public void testGetEntryByRootId() + { + ConfigurationEntry root = _handler.getEntry(_rootId); + assertEquals("Unexpected root id", _rootId, root.getId()); + assertEquals("Unexpected children", Collections.singleton(_portEntryId), root.getChildrenIds()); + } + + public void testGetEntryByPortId() + { + ConfigurationEntry portEntry = _handler.getEntry(_portEntryId); + assertEquals("Unexpected entry id", _portEntryId, portEntry.getId()); + assertTrue("Unexpected children", portEntry.getChildrenIds().isEmpty()); + assertEquals("Unexpected state", State.QUIESCED, portEntry.getAttributes().get(Port.STATE)); + } + + public void testGetEntryByCLIConnectorPortId() + { + _options.setManagementModeJmxPortOverride(9090); + _handler = new ManagementModeStoreHandler(_store, _options); + + UUID optionsPort = getOptionsPortId(); + ConfigurationEntry portEntry = _handler.getEntry(optionsPort); + assertCLIPortEntry(portEntry, optionsPort, Protocol.JMX_RMI); + } + + public void testGetEntryByCLIHttpPortId() + { + _options.setManagementModeHttpPortOverride(9090); + _handler = new ManagementModeStoreHandler(_store, _options); + + UUID optionsPort = getOptionsPortId(); + ConfigurationEntry portEntry = _handler.getEntry(optionsPort); + assertCLIPortEntry(portEntry, optionsPort, Protocol.HTTP); + } + + public void testHttpPortEntryIsQuiesced() + { + Map attributes = new HashMap(); + attributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.HTTP)); + when(_portEntry.getAttributes()).thenReturn(attributes); + _options.setManagementModeHttpPortOverride(9090); + _handler = new ManagementModeStoreHandler(_store, _options); + + ConfigurationEntry portEntry = _handler.getEntry(_portEntryId); + assertEquals("Unexpected state", State.QUIESCED, portEntry.getAttributes().get(Port.STATE)); + } + + public void testRmiPortEntryIsQuiesced() + { + Map attributes = new HashMap(); + attributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.RMI)); + when(_portEntry.getAttributes()).thenReturn(attributes); + _options.setManagementModeRmiPortOverride(9090); + _handler = new ManagementModeStoreHandler(_store, _options); + + ConfigurationEntry portEntry = _handler.getEntry(_portEntryId); + assertEquals("Unexpected state", State.QUIESCED, portEntry.getAttributes().get(Port.STATE)); + } + + public void testConnectorPortEntryIsQuiesced() + { + Map attributes = new HashMap(); + attributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.JMX_RMI)); + when(_portEntry.getAttributes()).thenReturn(attributes); + _options.setManagementModeRmiPortOverride(9090); + _handler = new ManagementModeStoreHandler(_store, _options); + + ConfigurationEntry portEntry = _handler.getEntry(_portEntryId); + assertEquals("Unexpected state", State.QUIESCED, portEntry.getAttributes().get(Port.STATE)); + } + + public void testVirtualHostEntryIsNotQuiescedByDefault() + { + virtualHostEntryQuiescedStatusTestImpl(false); + } + + public void testVirtualHostEntryIsQuiescedWhenRequested() + { + virtualHostEntryQuiescedStatusTestImpl(true); + } + + private void virtualHostEntryQuiescedStatusTestImpl(boolean mmQuiesceVhosts) + { + UUID virtualHostId = UUID.randomUUID(); + ConfigurationEntry virtualHost = mock(ConfigurationEntry.class); + when(virtualHost.getId()).thenReturn(virtualHostId); + when(virtualHost.getType()).thenReturn(VirtualHost.class.getSimpleName()); + Map attributes = new HashMap(); + attributes.put(VirtualHost.CONFIG_PATH, "/path/to/host.xml"); + when(virtualHost.getAttributes()).thenReturn(attributes); + when(_store.getEntry(virtualHostId)).thenReturn(virtualHost); + when(_root.getChildrenIds()).thenReturn(new HashSet(Arrays.asList(_portEntryId, virtualHostId))); + + State expectedState = mmQuiesceVhosts ? State.QUIESCED : null; + if(mmQuiesceVhosts) + { + _options.setManagementModeQuiesceVirtualHosts(mmQuiesceVhosts); + } + + _handler = new ManagementModeStoreHandler(_store, _options); + + ConfigurationEntry hostEntry = _handler.getEntry(virtualHostId); + Map hostAttributes = hostEntry.getAttributes(); + assertEquals("Unexpected state", expectedState, hostAttributes.get(VirtualHost.STATE)); + hostAttributes.remove(VirtualHost.STATE); + assertEquals("Unexpected attributes", attributes, hostAttributes); + } + + @SuppressWarnings("unchecked") + private void assertCLIPortEntry(ConfigurationEntry portEntry, UUID optionsPort, Protocol protocol) + { + assertEquals("Unexpected entry id", optionsPort, portEntry.getId()); + assertTrue("Unexpected children", portEntry.getChildrenIds().isEmpty()); + Map attributes = portEntry.getAttributes(); + assertEquals("Unexpected name", "MANAGEMENT-MODE-PORT-" + protocol.name(), attributes.get(Port.NAME)); + assertEquals("Unexpected protocol", Collections.singleton(protocol), new HashSet( + (Collection) attributes.get(Port.PROTOCOLS))); + } + + public void testSavePort() + { + _options.setManagementModeHttpPortOverride(1000); + _options.setManagementModeRmiPortOverride(2000); + _options.setManagementModeJmxPortOverride(3000); + _handler = new ManagementModeStoreHandler(_store, _options); + + Map attributes = new HashMap(); + attributes.put(Port.NAME, "TEST"); + ConfigurationEntry configurationEntry = new ConfigurationEntry(_portEntryId, Port.class.getSimpleName(), attributes, + Collections. emptySet(), null); + _handler.save(configurationEntry); + verify(_store).save(any(ConfigurationEntry.class)); + } + + public void testSaveRoot() + { + _options.setManagementModeHttpPortOverride(1000); + _options.setManagementModeRmiPortOverride(2000); + _options.setManagementModeJmxPortOverride(3000); + _handler = new ManagementModeStoreHandler(_store, _options); + + ConfigurationEntry root = _handler.getRootEntry(); + Map attributes = new HashMap(); + attributes.put(Broker.NAME, "TEST"); + ConfigurationEntry configurationEntry = new ConfigurationEntry(_rootId, Broker.class.getSimpleName(), attributes, + root.getChildrenIds(), null); + _handler.save(configurationEntry); + verify(_store).save(any(ConfigurationEntry.class)); + } + + public void testSaveCLIHttpPort() + { + _options.setManagementModeHttpPortOverride(1000); + _handler = new ManagementModeStoreHandler(_store, _options); + + UUID portId = getOptionsPortId(); + Map attributes = new HashMap(); + attributes.put(Port.NAME, "TEST"); + ConfigurationEntry configurationEntry = new ConfigurationEntry(portId, Port.class.getSimpleName(), attributes, + Collections. emptySet(), null); + try + { + _handler.save(configurationEntry); + fail("Exception should be thrown on trying to save CLI port"); + } + catch (IllegalConfigurationException e) + { + // pass + } + } + + public void testRemove() + { + _options.setManagementModeHttpPortOverride(1000); + _handler = new ManagementModeStoreHandler(_store, _options); + + _handler.remove(_portEntryId); + verify(_store).remove(_portEntryId); + } + + public void testRemoveCLIPort() + { + _options.setManagementModeHttpPortOverride(1000); + _handler = new ManagementModeStoreHandler(_store, _options); + UUID portId = getOptionsPortId(); + try + { + _handler.remove(portId); + fail("Exception should be thrown on trying to remove CLI port"); + } + catch (IllegalConfigurationException e) + { + // pass + } + } + + private UUID getOptionsPortId() + { + ConfigurationEntry root = _handler.getRootEntry(); + assertEquals("Unexpected root id", _rootId, root.getId()); + Collection childrenIds = root.getChildrenIds(); + + childrenIds.remove(_portEntryId); + UUID optionsPort = childrenIds.iterator().next(); + return optionsPort; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.java new file mode 100644 index 0000000000..d7ecaeafd2 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/MemoryConfigurationEntryStoreTest.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.configuration.store; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.Broker; +import org.codehaus.jackson.map.ObjectMapper; + +public class MemoryConfigurationEntryStoreTest extends ConfigurationEntryStoreTestCase +{ + + @Override + protected ConfigurationEntryStore createStore(UUID brokerId, Map brokerAttributes) throws Exception + { + Map broker = new HashMap(); + broker.put(Broker.ID, brokerId); + broker.putAll(brokerAttributes); + ObjectMapper mapper = new ObjectMapper(); + + return new MemoryConfigurationEntryStore(mapper.writeValueAsString(broker), Collections.emptyMap()); + } + + @Override + protected void addConfiguration(UUID id, String type, Map attributes) + { + ConfigurationEntryStore store = getStore(); + store.save(new ConfigurationEntry(id, type, attributes, Collections. emptySet(), store)); + } + + public void testCreateWithNullLocationAndNullInitialStore() + { + try + { + new MemoryConfigurationEntryStore(null, null, Collections.emptyMap()); + fail("Cannot create a memory store without either initial store or path to an initial store file"); + } + catch(IllegalConfigurationException e) + { + // pass + } + } + + public void testCreateWithNullJson() + { + MemoryConfigurationEntryStore store = new MemoryConfigurationEntryStore(null, Collections.emptyMap()); + + ConfigurationEntry root = store.getRootEntry(); + assertNotNull("Root entry is not found", root); + } + + public void testOpenInMemoryWithInitialStore() throws Exception + { + UUID brokerId = UUID.randomUUID(); + Map brokerAttributes = new HashMap(); + brokerAttributes.put(Broker.NAME, getTestName()); + MemoryConfigurationEntryStore initialStoreFile = (MemoryConfigurationEntryStore)createStore(brokerId, brokerAttributes); + MemoryConfigurationEntryStore store = new MemoryConfigurationEntryStore(null, initialStoreFile, Collections.emptyMap()); + + ConfigurationEntry root = store.getRootEntry(); + assertNotNull("Root entry is not found", root); + assertEquals("Unexpected root entry", brokerId, root.getId()); + Map attributes = root.getAttributes(); + assertNotNull("Attributes not found", attributes); + assertEquals("Unexpected number of attriburtes", 1, attributes.size()); + assertEquals("Unexpected name attribute", getTestName(), attributes.get(Broker.NAME)); + } + + + public void testOpenWithDefaultInitialStore() throws Exception + { + // check whether QPID_HOME JVM system property is set + if (QPID_HOME == null) + { + // set the properties in order to resolve the defaults store settings + setTestSystemProperty("QPID_HOME", TMP_FOLDER); + setTestSystemProperty("QPID_WORK", TMP_FOLDER + File.separator + "work"); + } + MemoryConfigurationEntryStore initialStore = new MemoryConfigurationEntryStore(BrokerOptions.DEFAULT_INITIAL_CONFIG_LOCATION, null, new BrokerOptions().getConfigProperties()); + ConfigurationEntry initialStoreRoot = initialStore.getRootEntry(); + assertNotNull("Initial store root entry is not found", initialStoreRoot); + + MemoryConfigurationEntryStore store = new MemoryConfigurationEntryStore(null, initialStore, Collections.emptyMap()); + + ConfigurationEntry root = store.getRootEntry(); + assertNotNull("Root entry is not found", root); + + assertEquals("Unexpected broker attributes", initialStoreRoot.getAttributes(), root.getAttributes()); + assertEquals("Unexpected broker children", initialStoreRoot.getChildrenIds(), root.getChildrenIds()); + } + + public void testGetVersion() + { + assertEquals("Unexpected version", 1, getStore().getVersion()); + } + + public void testGetType() + { + assertEquals("Unexpected type", "memory", getStore().getType()); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListenerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListenerTest.java new file mode 100644 index 0000000000..c23c4715e8 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/store/StoreConfigurationChangeListenerTest.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.configuration.store; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.when; + +import java.util.UUID; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.ConfiguredObject; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +public class StoreConfigurationChangeListenerTest extends QpidTestCase +{ + private ConfigurationEntryStore _store; + private StoreConfigurationChangeListener _listener; + + protected void setUp() throws Exception + { + super.setUp(); + _store = mock(ConfigurationEntryStore.class); + _listener = new StoreConfigurationChangeListener(_store); + } + + public void testStateChanged() + { + notifyBrokerStarted(); + UUID id = UUID.randomUUID(); + ConfiguredObject object = mock(VirtualHost.class); + when(object.getId()).thenReturn(id); + _listener.stateChanged(object, State.ACTIVE, State.DELETED); + verify(_store).remove(id); + } + + public void testChildAdded() + { + notifyBrokerStarted(); + Broker broker = mock(Broker.class); + VirtualHost child = mock(VirtualHost.class); + _listener.childAdded(broker, child); + verify(_store).save(any(ConfigurationEntry.class), any(ConfigurationEntry.class)); + } + + public void testChildRemoved() + { + notifyBrokerStarted(); + Broker broker = mock(Broker.class); + VirtualHost child = mock(VirtualHost.class); + _listener.childRemoved(broker, child); + verify(_store).save(any(ConfigurationEntry.class)); + } + + public void testAttributeSet() + { + notifyBrokerStarted(); + Broker broker = mock(Broker.class); + _listener.attributeSet(broker, Broker.QUEUE_FLOW_CONTROL_SIZE_BYTES, null, 1); + verify(_store).save(any(ConfigurationEntry.class)); + } + + public void testChildAddedForVirtualHost() + { + notifyBrokerStarted(); + + VirtualHost object = mock(VirtualHost.class); + Queue queue = mock(Queue.class); + _listener.childAdded(object, queue); + verifyNoMoreInteractions(_store); + } + + private void notifyBrokerStarted() + { + Broker broker = mock(Broker.class); + _listener.stateChanged(broker, State.INITIALISING, State.ACTIVE); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/updater/TaskExecutorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/updater/TaskExecutorTest.java new file mode 100644 index 0000000000..cd6302d55b --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/configuration/updater/TaskExecutorTest.java @@ -0,0 +1,296 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.updater; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import javax.security.auth.Subject; + +import junit.framework.TestCase; + +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.NullRootMessageLogger; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.TestLogActor; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.security.SecurityManager; + +public class TaskExecutorTest extends TestCase +{ + private TaskExecutor _executor; + + protected void setUp() throws Exception + { + super.setUp(); + _executor = new TaskExecutor(); + } + + protected void tearDown() throws Exception + { + try + { + _executor.stopImmediately(); + } + finally + { + super.tearDown(); + } + } + + public void testGetState() + { + assertEquals("Unxpected initial state", State.INITIALISING, _executor.getState()); + } + + public void testStart() + { + _executor.start(); + assertEquals("Unxpected started state", State.ACTIVE, _executor.getState()); + } + + public void testStopImmediately() throws Exception + { + _executor.start(); + final CountDownLatch submitLatch = new CountDownLatch(2); + final CountDownLatch waitForCallLatch = new CountDownLatch(1); + final BlockingQueue submitExceptions = new LinkedBlockingQueue(); + + Runnable runnable = new Runnable() + { + @Override + public void run() + { + try + { + Future f = _executor.submit(new NeverEndingCallable(waitForCallLatch)); + submitLatch.countDown(); + f.get(); + } + catch (Exception e) + { + if (e instanceof ExecutionException) + { + e = (Exception) e.getCause(); + } + submitExceptions.add(e); + } + } + }; + new Thread(runnable).start(); + new Thread(runnable).start(); + assertTrue("Tasks have not been submitted", submitLatch.await(1000, TimeUnit.MILLISECONDS)); + assertTrue("The first task has not been triggered", waitForCallLatch.await(1000, TimeUnit.MILLISECONDS)); + + _executor.stopImmediately(); + assertEquals("Unxpected stopped state", State.STOPPED, _executor.getState()); + + Exception e = submitExceptions.poll(1000l, TimeUnit.MILLISECONDS); + assertNotNull("The task execution was not interrupted or cancelled", e); + Exception e2 = submitExceptions.poll(1000l, TimeUnit.MILLISECONDS); + assertNotNull("The task execution was not interrupted or cancelled", e2); + + assertTrue("One of the exceptions should be CancellationException:", e2 instanceof CancellationException + || e instanceof CancellationException); + assertTrue("One of the exceptions should be InterruptedException:", e2 instanceof InterruptedException + || e instanceof InterruptedException); + } + + public void testStop() + { + _executor.start(); + _executor.stop(); + assertEquals("Unxpected stopped state", State.STOPPED, _executor.getState()); + } + + public void testSubmitAndWait() throws Exception + { + _executor.start(); + Object result = _executor.submitAndWait(new Callable() + { + @Override + public String call() throws Exception + { + return "DONE"; + } + }); + assertEquals("Unexpected task execution result", "DONE", result); + } + + public void testSubmitAndWaitInNotAuthorizedContext() + { + _executor.start(); + Object subject = _executor.submitAndWait(new SubjectRetriever()); + assertNull("Subject must be null", subject); + } + + public void testSubmitAndWaitInAuthorizedContext() + { + _executor.start(); + Subject subject = new Subject(); + Object result = Subject.doAs(subject, new PrivilegedAction() + { + @Override + public Object run() + { + return _executor.submitAndWait(new SubjectRetriever()); + } + }); + assertEquals("Unexpected subject", subject, result); + } + + public void testSubmitAndWaitInAuthorizedContextWithNullSubject() + { + _executor.start(); + Object result = Subject.doAs(null, new PrivilegedAction() + { + @Override + public Object run() + { + return _executor.submitAndWait(new SubjectRetriever()); + } + }); + assertEquals("Unexpected subject", null, result); + } + + public void testSubmitAndWaitReThrowsOriginalRuntimeException() + { + final RuntimeException exception = new RuntimeException(); + _executor.start(); + try + { + _executor.submitAndWait(new Callable() + { + + @Override + public Void call() throws Exception + { + throw exception; + } + }); + fail("Exception is expected"); + } + catch (Exception e) + { + assertEquals("Unexpected exception", exception, e); + } + } + + public void testSubmitAndWaitPassesOriginalCheckedException() + { + final Exception exception = new Exception(); + _executor.start(); + try + { + _executor.submitAndWait(new Callable() + { + + @Override + public Void call() throws Exception + { + throw exception; + } + }); + fail("Exception is expected"); + } + catch (Exception e) + { + assertEquals("Unexpected exception", exception, e.getCause()); + } + } + + public void testSubmitAndWaitCurrentActorAndSecurityManagerSubjectAreRespected() throws Exception + { + _executor.start(); + LogActor actor = new TestLogActor(new NullRootMessageLogger()); + Subject subject = new Subject(); + Subject currentSecurityManagerSubject = SecurityManager.getThreadSubject(); + final AtomicReference taskLogActor = new AtomicReference(); + final AtomicReference taskSubject = new AtomicReference(); + try + { + CurrentActor.set(actor); + SecurityManager.setThreadSubject(subject); + _executor.submitAndWait(new Callable() + { + @Override + public Void call() throws Exception + { + taskLogActor.set(CurrentActor.get()); + taskSubject.set(SecurityManager.getThreadSubject()); + return null; + } + }); + } + finally + { + SecurityManager.setThreadSubject(currentSecurityManagerSubject); + CurrentActor.remove(); + } + assertEquals("Unexpected task log actor", actor, taskLogActor.get()); + assertEquals("Unexpected security manager subject", subject, taskSubject.get()); + } + + private class SubjectRetriever implements Callable + { + @Override + public Subject call() throws Exception + { + return Subject.getSubject(AccessController.getContext()); + } + } + + private class NeverEndingCallable implements Callable + { + private CountDownLatch _waitLatch; + + public NeverEndingCallable(CountDownLatch waitLatch) + { + super(); + _waitLatch = waitLatch; + } + + @Override + public Void call() throws Exception + { + if (_waitLatch != null) + { + _waitLatch.countDown(); + } + + // wait forever + synchronized (this) + { + this.wait(); + } + return null; + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/DefaultExchangeFactoryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/DefaultExchangeFactoryTest.java new file mode 100644 index 0000000000..86ae3e6e9c --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/DefaultExchangeFactoryTest.java @@ -0,0 +1,226 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.exchange; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +@SuppressWarnings("rawtypes") +public class DefaultExchangeFactoryTest extends QpidTestCase +{ + private DirectExchangeType _directExchangeType; + private TopicExchangeType _topicExchangeType; + private FanoutExchangeType _fanoutExchangeType; + private HeadersExchangeType _headersExchangeType; + + private List _stubbedExchangeTypes; + + protected void setUp() throws Exception + { + super.setUp(); + + _directExchangeType = new DirectExchangeType(); + _topicExchangeType = new TopicExchangeType(); + _fanoutExchangeType = new FanoutExchangeType(); + _headersExchangeType = new HeadersExchangeType(); + _stubbedExchangeTypes = new ArrayList(); + } + + public void testCreateDefaultExchangeFactory() + { + _stubbedExchangeTypes.add(_directExchangeType); + _stubbedExchangeTypes.add(_topicExchangeType); + _stubbedExchangeTypes.add(_fanoutExchangeType); + _stubbedExchangeTypes.add(_headersExchangeType); + + DefaultExchangeFactory factory = new TestExchangeFactory(); + + Collection> registeredTypes = factory.getRegisteredTypes(); + assertEquals("Unexpected number of exchange types", _stubbedExchangeTypes.size(), registeredTypes.size()); + assertTrue("Direct exchange type is not found", registeredTypes.contains(_directExchangeType)); + assertTrue("Fanout exchange type is not found", registeredTypes.contains(_fanoutExchangeType)); + assertTrue("Topic exchange type is not found", registeredTypes.contains(_topicExchangeType)); + assertTrue("Headers exchange type is not found", registeredTypes.contains(_headersExchangeType)); + } + + public void testCreateDefaultExchangeFactoryWithoutAllBaseExchangeTypes() + { + try + { + new TestExchangeFactory(); + fail("Cannot create factory without all base classes"); + } + catch (IllegalStateException e) + { + // pass + } + } + + public void testCreateDefaultExchangeFactoryWithoutDireactExchangeType() + { + _stubbedExchangeTypes.add(_topicExchangeType); + _stubbedExchangeTypes.add(_fanoutExchangeType); + _stubbedExchangeTypes.add(_headersExchangeType); + + try + { + new TestExchangeFactory(); + fail("Cannot create factory without all base classes"); + } + catch (IllegalStateException e) + { + assertEquals("Unexpected exception message", "Did not find expected exchange type: " + _directExchangeType.getType(), e.getMessage()); + } + } + + public void testCreateDefaultExchangeFactoryWithoutTopicExchangeType() + { + _stubbedExchangeTypes.add(_directExchangeType); + _stubbedExchangeTypes.add(_fanoutExchangeType); + _stubbedExchangeTypes.add(_headersExchangeType); + + try + { + new TestExchangeFactory(); + fail("Cannot create factory without all base classes"); + } + catch (IllegalStateException e) + { + assertEquals("Unexpected exception message", "Did not find expected exchange type: " + _topicExchangeType.getType(), e.getMessage()); + } + } + + public void testCreateDefaultExchangeFactoryWithoutFanoutExchangeType() + { + _stubbedExchangeTypes.add(_directExchangeType); + _stubbedExchangeTypes.add(_topicExchangeType); + _stubbedExchangeTypes.add(_headersExchangeType); + + try + { + new TestExchangeFactory(); + fail("Cannot create factory without all base classes"); + } + catch (IllegalStateException e) + { + assertEquals("Unexpected exception message", "Did not find expected exchange type: " + _fanoutExchangeType.getType(), e.getMessage()); + } + } + + public void testCreateDefaultExchangeFactoryWithoutHeadersExchangeType() + { + _stubbedExchangeTypes.add(_directExchangeType); + _stubbedExchangeTypes.add(_topicExchangeType); + _stubbedExchangeTypes.add(_fanoutExchangeType); + + try + { + new TestExchangeFactory(); + fail("Cannot create factory without all base classes"); + } + catch (IllegalStateException e) + { + assertEquals("Unexpected exception message", "Did not find expected exchange type: " + _headersExchangeType.getType(), e.getMessage()); + } + } + + public void testCreateDefaultExchangeFactoryWithDuplicateExchangeTypeName() + { + _stubbedExchangeTypes.add(_directExchangeType); + _stubbedExchangeTypes.add(_directExchangeType); + + try + { + new TestExchangeFactory(); + fail("Cannot create factory with duplicate exchange type names"); + } + catch (IllegalStateException e) + { + assertTrue( "Unexpected exception message", e.getMessage().contains("ExchangeType with type name '" + + _directExchangeType.getType() + "' is already registered using class '" + + DirectExchangeType.class.getName())); + } + } + + public void testCreateDefaultExchangeFactoryWithCustomExchangeType() + { + ExchangeType customExchangeType = new ExchangeType() + { + @Override + public String getType() + { + return "my-custom-exchange"; + } + + @Override + public Exchange newInstance(UUID id, VirtualHost host, String name, boolean durable, + boolean autoDelete) throws AMQException + { + return null; + } + + @Override + public String getDefaultExchangeName() + { + return null; + } + }; + + _stubbedExchangeTypes.add(customExchangeType); + _stubbedExchangeTypes.add(_directExchangeType); + _stubbedExchangeTypes.add(_topicExchangeType); + _stubbedExchangeTypes.add(_fanoutExchangeType); + _stubbedExchangeTypes.add(_headersExchangeType); + + DefaultExchangeFactory factory = new TestExchangeFactory(); + + Collection> registeredTypes = factory.getRegisteredTypes(); + assertEquals("Unexpected number of exchange types", _stubbedExchangeTypes.size(), registeredTypes.size()); + assertTrue("Direct exchange type is not found", registeredTypes.contains(_directExchangeType)); + assertTrue("Fanout exchange type is not found", registeredTypes.contains(_fanoutExchangeType)); + assertTrue("Topic exchange type is not found", registeredTypes.contains(_topicExchangeType)); + assertTrue("Headers exchange type is not found", registeredTypes.contains(_headersExchangeType)); + assertTrue("Custom exchange type is not found", registeredTypes.contains(customExchangeType)); + } + + private final class TestExchangeFactory extends DefaultExchangeFactory + { + private TestExchangeFactory() + { + super(null); + } + + @Override + protected Iterable loadExchangeTypes() + { + return _stubbedExchangeTypes; + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/FanoutExchangeTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/FanoutExchangeTest.java new file mode 100644 index 0000000000..7335d43b2e --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/FanoutExchangeTest.java @@ -0,0 +1,194 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 static org.mockito.Matchers.any; +import static org.mockito.Matchers.anySet; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import junit.framework.TestCase; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class FanoutExchangeTest extends TestCase +{ + private FanoutExchange _exchange; + private VirtualHost _virtualHost; + + public void setUp() throws AMQException + { + CurrentActor.setDefault(mock(LogActor.class)); + + _exchange = new FanoutExchange(); + _virtualHost = mock(VirtualHost.class); + SecurityManager securityManager = mock(SecurityManager.class); + when(_virtualHost.getSecurityManager()).thenReturn(securityManager); + when(securityManager.authoriseBind(any(Exchange.class), any(AMQQueue.class), anyString())).thenReturn(true); + when(securityManager.authoriseUnbind(any(Exchange.class), anyString(), any(AMQQueue.class))).thenReturn(true); + + _exchange.initialise(UUID.randomUUID(), _virtualHost, "test", false, false); + } + + public void testIsBoundStringMapAMQQueueWhenQueueIsNull() + { + assertFalse("calling isBound(AMQShortString,FieldTable,AMQQueue) with null queue should return false", + _exchange.isBound((String) null, (Map) null, (AMQQueue) null)); + } + + public void testIsBoundStringAMQQueueWhenQueueIsNull() + { + assertFalse("calling isBound(AMQShortString,AMQQueue) with null queue should return false", + _exchange.isBound((String) null, (AMQQueue) null)); + } + + public void testIsBoundAMQQueueWhenQueueIsNull() + { + assertFalse("calling isBound(AMQQueue) with null queue should return false", _exchange.isBound((AMQQueue) null)); + } + + public void testIsBoundStringMapAMQQueue() throws AMQSecurityException, AMQInternalException + { + AMQQueue queue = bindQueue(); + assertTrue("Should return true for a bound queue", + _exchange.isBound("matters", null, queue)); + } + + public void testIsBoundStringAMQQueue() throws AMQSecurityException, AMQInternalException + { + AMQQueue queue = bindQueue(); + assertTrue("Should return true for a bound queue", + _exchange.isBound("matters", queue)); + } + + public void testIsBoundAMQQueue() throws AMQSecurityException, AMQInternalException + { + AMQQueue queue = bindQueue(); + assertTrue("Should return true for a bound queue", + _exchange.isBound(queue)); + } + + private AMQQueue bindQueue() throws AMQSecurityException, AMQInternalException + { + AMQQueue queue = mockQueue(); + _exchange.addBinding("matters", queue, null); + return queue; + } + + private AMQQueue mockQueue() + { + AMQQueue queue = mock(AMQQueue.class); + when(queue.getVirtualHost()).thenReturn(_virtualHost); + return queue; + } + + public void testRoutingWithSelectors() throws Exception + { + AMQQueue queue1 = mockQueue(); + AMQQueue queue2 = mockQueue(); + + _exchange.addBinding("key",queue1, null); + _exchange.addBinding("key",queue2, null); + + + List result = _exchange.route(mockMessage(true)); + + assertEquals("Expected message to be routed to both queues", 2, result.size()); + assertTrue("Expected queue1 to be routed to", result.contains(queue1)); + assertTrue("Expected queue2 to be routed to", result.contains(queue2)); + + _exchange.addBinding("key2",queue2, Collections.singletonMap(AMQPFilterTypes.JMS_SELECTOR.toString(),(Object)"select = True")); + + + result = _exchange.route(mockMessage(true)); + + assertEquals("Expected message to be routed to both queues", 2, result.size()); + assertTrue("Expected queue1 to be routed to", result.contains(queue1)); + assertTrue("Expected queue2 to be routed to", result.contains(queue2)); + + _exchange.removeBinding("key",queue2,null); + + result = _exchange.route(mockMessage(true)); + + assertEquals("Expected message to be routed to both queues", 2, result.size()); + assertTrue("Expected queue1 to be routed to", result.contains(queue1)); + assertTrue("Expected queue2 to be routed to", result.contains(queue2)); + + + result = _exchange.route(mockMessage(false)); + + assertEquals("Expected message to be routed to queue1 only", 1, result.size()); + assertTrue("Expected queue1 to be routed to", result.contains(queue1)); + assertFalse("Expected queue2 not to be routed to", result.contains(queue2)); + + _exchange.addBinding("key",queue2, Collections.singletonMap(AMQPFilterTypes.JMS_SELECTOR.toString(),(Object)"select = False")); + + + result = _exchange.route(mockMessage(false)); + assertEquals("Expected message to be routed to both queues", 2, result.size()); + assertTrue("Expected queue1 to be routed to", result.contains(queue1)); + assertTrue("Expected queue2 to be routed to", result.contains(queue2)); + + + } + + private InboundMessage mockMessage(boolean val) + { + final AMQMessageHeader header = mock(AMQMessageHeader.class); + when(header.containsHeader("select")).thenReturn(true); + when(header.getHeader("select")).thenReturn(val); + when(header.getHeaderNames()).thenReturn(Collections.singleton("select")); + when(header.containsHeaders(anySet())).then(new Answer() + { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable + { + final Set names = (Set) invocation.getArguments()[0]; + return names.size() == 1 && names.contains("select"); + + } + }); + final InboundMessage inboundMessage = mock(InboundMessage.class); + when(inboundMessage.getMessageHeader()).thenReturn(header); + return inboundMessage; + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java new file mode 100644 index 0000000000..833df34fd8 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/HeadersBindingTest.java @@ -0,0 +1,334 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 junit.framework.TestCase; + +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.queue.MockAMQQueue; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + */ +public class HeadersBindingTest extends TestCase +{ + + private class MockHeader implements AMQMessageHeader + { + + private final Map _headers = new HashMap(); + + public String getCorrelationId() + { + return null; + } + + public long getExpiration() + { + return 0; + } + + public String getUserId() + { + return null; + } + + public String getAppId() + { + return null; + } + + public String getMessageId() + { + return null; + } + + public String getMimeType() + { + return null; + } + + public String getEncoding() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public byte getPriority() + { + return 0; + } + + public long getTimestamp() + { + return 0; + } + + public String getType() + { + return null; + } + + public String getReplyTo() + { + return null; + } + + public String getReplyToExchange() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public String getReplyToRoutingKey() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public Object getHeader(String name) + { + return _headers.get(name); + } + + public boolean containsHeaders(Set names) + { + return _headers.keySet().containsAll(names); + } + + @Override + public Collection getHeaderNames() + { + return _headers.keySet(); + } + + public boolean containsHeader(String name) + { + return _headers.containsKey(name); + } + + public void setString(String key, String value) + { + setObject(key,value); + } + + public void setObject(String key, Object value) + { + _headers.put(key,value); + } + } + + private Map bindHeaders = new HashMap(); + private MockHeader matchHeaders = new MockHeader(); + private int _count = 0; + private MockAMQQueue _queue; + + protected void setUp() + { + _count++; + _queue = new MockAMQQueue(getQueueName()); + } + + protected String getQueueName() + { + return "Queue" + _count; + } + + public void testDefault_1() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testDefault_2() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testDefault_3() + { + bindHeaders.put("A", "Value of A"); + + matchHeaders.setString("A", "Altered value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertFalse(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAll_1() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAll_2() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertFalse(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAll_3() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAll_4() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + matchHeaders.setString("C", "Value of C"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAll_5() + { + bindHeaders.put("X-match", "all"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Altered value of B"); + matchHeaders.setString("C", "Value of C"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertFalse(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_1() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + + matchHeaders.setString("A", "Value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_2() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_3() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_4() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Value of B"); + matchHeaders.setString("C", "Value of C"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_5() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Value of A"); + matchHeaders.setString("B", "Altered value of B"); + matchHeaders.setString("C", "Value of C"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertTrue(new HeadersBinding(b).matches(matchHeaders)); + } + + public void testAny_6() + { + bindHeaders.put("X-match", "any"); + bindHeaders.put("A", "Value of A"); + bindHeaders.put("B", "Value of B"); + + matchHeaders.setString("A", "Altered value of A"); + matchHeaders.setString("B", "Altered value of B"); + matchHeaders.setString("C", "Value of C"); + + Binding b = new Binding(null, getQueueName(), _queue, null, bindHeaders); + assertFalse(new HeadersBinding(b).matches(matchHeaders)); + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(HeadersBindingTest.class); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java new file mode 100644 index 0000000000..0f1ab65244 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/HeadersExchangeTest.java @@ -0,0 +1,252 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import junit.framework.TestCase; +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anySet; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class HeadersExchangeTest extends TestCase +{ + private HeadersExchange _exchange; + private VirtualHost _virtualHost; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + CurrentActor.setDefault(mock(LogActor.class)); + _exchange = new HeadersExchange(); + _virtualHost = mock(VirtualHost.class); + SecurityManager securityManager = mock(SecurityManager.class); + when(_virtualHost.getSecurityManager()).thenReturn(securityManager); + when(securityManager.authoriseBind(any(Exchange.class), any(AMQQueue.class), anyString())).thenReturn(true); + when(securityManager.authoriseUnbind(any(Exchange.class), anyString(), any(AMQQueue.class))).thenReturn(true); + + _exchange.initialise(UUID.randomUUID(), _virtualHost, "test", false, false); + + } + + protected void routeAndTest(InboundMessage msg, AMQQueue... expected) throws Exception + { + List results = _exchange.route(msg); + List unexpected = new ArrayList(results); + unexpected.removeAll(Arrays.asList(expected)); + assertTrue("Message delivered to unexpected queues: " + unexpected, unexpected.isEmpty()); + List missing = new ArrayList(Arrays.asList(expected)); + missing.removeAll(results); + assertTrue("Message not delivered to expected queues: " + missing, missing.isEmpty()); + assertTrue("Duplicates " + results, results.size()==(new HashSet(results)).size()); + } + + + private AMQQueue createAndBind(final String name, String... arguments) + throws Exception + { + return createAndBind(name, getArgsMapFromStrings(arguments)); + } + + private Map getArgsMapFromStrings(String... arguments) + { + Map map = new HashMap(); + + for(String arg : arguments) + { + if(arg.contains("=")) + { + String[] keyValue = arg.split("=",2); + map.put(keyValue[0],keyValue[1]); + } + else + { + map.put(arg,null); + } + } + return map; + } + + private AMQQueue createAndBind(final String name, Map arguments) + throws Exception + { + AMQQueue q = create(name); + bind(name, arguments, q); + return q; + } + + private void bind(String bindingKey, Map arguments, AMQQueue q) + throws AMQSecurityException, AMQInternalException + { + _exchange.addBinding(bindingKey,q,arguments); + } + + private AMQQueue create(String name) + { + AMQQueue q = mock(AMQQueue.class); + when(q.toString()).thenReturn(name); + when(q.getVirtualHost()).thenReturn(_virtualHost); + return q; + } + + + public void testSimple() throws Exception + { + AMQQueue q1 = createAndBind("Q1", "F0000"); + AMQQueue q2 = createAndBind("Q2", "F0000=Aardvark"); + AMQQueue q3 = createAndBind("Q3", "F0001"); + AMQQueue q4 = createAndBind("Q4", "F0001=Bear"); + AMQQueue q5 = createAndBind("Q5", "F0000", "F0001"); + AMQQueue q6 = createAndBind("Q6", "F0000=Aardvark", "F0001=Bear"); + AMQQueue q7 = createAndBind("Q7", "F0000", "F0001=Bear"); + AMQQueue q8 = createAndBind("Q8", "F0000=Aardvark", "F0001"); + + routeAndTest(mockMessage(getArgsMapFromStrings("F0000")), q1); + routeAndTest(mockMessage(getArgsMapFromStrings("F0000=Aardvark")), q1, q2); + routeAndTest(mockMessage(getArgsMapFromStrings("F0000=Aardvark", "F0001")), q1, q2, q3, q5, q8); + routeAndTest(mockMessage(getArgsMapFromStrings("F0000", "F0001=Bear")), q1, q3, q4, q5, q7); + routeAndTest(mockMessage(getArgsMapFromStrings("F0000=Aardvark", "F0001=Bear")), + q1, q2, q3, q4, q5, q6, q7, q8); + routeAndTest(mockMessage(getArgsMapFromStrings("F0002"))); + + } + + public void testAny() throws Exception + { + AMQQueue q1 = createAndBind("Q1", "F0000", "F0001", "X-match=any"); + AMQQueue q2 = createAndBind("Q2", "F0000=Aardvark", "F0001=Bear", "X-match=any"); + AMQQueue q3 = createAndBind("Q3", "F0000", "F0001=Bear", "X-match=any"); + AMQQueue q4 = createAndBind("Q4", "F0000=Aardvark", "F0001", "X-match=any"); + AMQQueue q5 = createAndBind("Q5", "F0000=Apple", "F0001", "X-match=any"); + + routeAndTest(mockMessage(getArgsMapFromStrings("F0000")), q1, q3); + routeAndTest(mockMessage(getArgsMapFromStrings("F0000=Aardvark")), q1, q2, q3, q4); + routeAndTest(mockMessage(getArgsMapFromStrings("F0000=Aardvark", "F0001")), q1, q2, q3, q4, q5); + routeAndTest(mockMessage(getArgsMapFromStrings("F0000", "F0001=Bear")), q1, q2, q3, q4, q5); + routeAndTest(mockMessage(getArgsMapFromStrings("F0000=Aardvark", "F0001=Bear")), q1, q2, q3, q4, q5); + routeAndTest(mockMessage(getArgsMapFromStrings("F0002"))); + } + + public void testOnUnbind() throws Exception + { + AMQQueue q1 = createAndBind("Q1", "F0000"); + AMQQueue q2 = createAndBind("Q2", "F0000=Aardvark"); + AMQQueue q3 = createAndBind("Q3", "F0001"); + + routeAndTest(mockMessage(getArgsMapFromStrings("F0000")), q1); + routeAndTest(mockMessage(getArgsMapFromStrings("F0000=Aardvark")), q1, q2); + routeAndTest(mockMessage(getArgsMapFromStrings("F0001")), q3); + + _exchange.removeBinding("Q1",q1,getArgsMapFromStrings("F0000")); + + routeAndTest(mockMessage(getArgsMapFromStrings("F0000"))); + routeAndTest(mockMessage(getArgsMapFromStrings("F0000=Aardvark")), q2); + } + + + public void testWithSelectors() throws Exception + { + AMQQueue q1 = create("Q1"); + AMQQueue q2 = create("Q2"); + bind("q1",getArgsMapFromStrings("F"), q1); + bind("q1select",getArgsMapFromStrings("F", AMQPFilterTypes.JMS_SELECTOR.toString()+"=F='1'"), q1); + bind("q2",getArgsMapFromStrings("F=1"), q2); + + routeAndTest(mockMessage(getArgsMapFromStrings("F")),q1); + + routeAndTest(mockMessage(getArgsMapFromStrings("F=1")),q1,q2); + + + AMQQueue q3 = create("Q3"); + bind("q3select",getArgsMapFromStrings("F", AMQPFilterTypes.JMS_SELECTOR.toString()+"=F='1'"), q3); + routeAndTest(mockMessage(getArgsMapFromStrings("F=1")),q1,q2,q3); + routeAndTest(mockMessage(getArgsMapFromStrings("F=2")),q1); + bind("q3select2",getArgsMapFromStrings("F", AMQPFilterTypes.JMS_SELECTOR.toString()+"=F='2'"), q3); + + routeAndTest(mockMessage(getArgsMapFromStrings("F=2")),q1,q3); + + } + + private InboundMessage mockMessage(final Map headerValues) + { + final AMQMessageHeader header = mock(AMQMessageHeader.class); + when(header.containsHeader(anyString())).then(new Answer() + { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable + { + return headerValues.containsKey((String) invocation.getArguments()[0]); + } + }); + when(header.getHeader(anyString())).then(new Answer() + { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable + { + return headerValues.get((String) invocation.getArguments()[0]); + } + }); + when(header.getHeaderNames()).thenReturn(headerValues.keySet()); + when(header.containsHeaders(anySet())).then(new Answer() + { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable + { + final Set names = (Set) invocation.getArguments()[0]; + return headerValues.keySet().containsAll(names); + + } + }); + final InboundMessage inboundMessage = mock(InboundMessage.class); + when(inboundMessage.getMessageHeader()).thenReturn(header); + return inboundMessage; + } + + public static junit.framework.Test suite() + { + return new junit.framework.TestSuite(HeadersExchangeTest.class); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/TopicExchangeTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/TopicExchangeTest.java new file mode 100644 index 0000000000..a84f5e1ecb --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/exchange/TopicExchangeTest.java @@ -0,0 +1,361 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.List; +import junit.framework.Assert; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TopicExchangeTest extends QpidTestCase +{ + + private TopicExchange _exchange; + private VirtualHost _vhost; + + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _exchange = new TopicExchange(); + _vhost = BrokerTestHelper.createVirtualHost(getName()); + } + + @Override + public void tearDown() throws Exception + { + try + { + if (_vhost != null) + { + _vhost.close(); + } + } + finally + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + } + + public void testNoRoute() throws AMQException + { + AMQQueue queue = _vhost.createQueue(UUIDGenerator.generateRandomUUID(), "a*#b", false, null, false, false, + false, null); + _exchange.registerQueue(new Binding(null, "a.*.#.b",queue, _exchange, null)); + + + routeMessage("a.b", 0l); + + Assert.assertEquals(0, queue.getMessageCount()); + } + + public void testDirectMatch() throws AMQException + { + AMQQueue queue = _vhost.createQueue(UUIDGenerator.generateRandomUUID(), "ab", false, null, false, false, + false, null); + _exchange.registerQueue(new Binding(null, "a.b",queue, _exchange, null)); + + + routeMessage("a.b",0l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 0l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + int queueCount = routeMessage("a.c",1l); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + } + + + public void testStarMatch() throws AMQException + { + AMQQueue queue = _vhost.createQueue(UUIDGenerator.generateRandomUUID(), "a*", false, null, false, false, false, null); + _exchange.registerQueue(new Binding(null, "a.*",queue, _exchange, null)); + + + routeMessage("a.b",0l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 0l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + + routeMessage("a.c",1l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 1l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + int queueCount = routeMessage("a",2l); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + } + + public void testHashMatch() throws AMQException + { + AMQQueue queue = _vhost.createQueue(UUIDGenerator.generateRandomUUID(), "a#", false, null, false, false, false, null); + _exchange.registerQueue(new Binding(null, "a.#",queue, _exchange, null)); + + + routeMessage("a.b.c",0l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 0l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + routeMessage("a.b",1l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 1l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + + routeMessage("a.c",2l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 2l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + routeMessage("a",3l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 3l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + + int queueCount = routeMessage("b", 4l); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + } + + + public void testMidHash() throws AMQException + { + AMQQueue queue = _vhost.createQueue(UUIDGenerator.generateRandomUUID(), "a", false, null, false, false, + false, null); + _exchange.registerQueue(new Binding(null, "a.*.#.b",queue, _exchange, null)); + + routeMessage("a.c.d.b",0l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 0l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + routeMessage("a.c.b",1l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 1l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testMatchafterHash() throws AMQException + { + AMQQueue queue = _vhost.createQueue(UUIDGenerator.generateRandomUUID(), "a#", false, null, false, false, + false, null); + _exchange.registerQueue(new Binding(null, "a.*.#.b.c",queue, _exchange, null)); + + + int queueCount = routeMessage("a.c.b.b",0l); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + + routeMessage("a.a.b.c",1l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 1l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + queueCount = routeMessage("a.b.c.b",2l); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + routeMessage("a.b.c.b.c",3l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 3l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + + public void testHashAfterHash() throws AMQException + { + AMQQueue queue = _vhost.createQueue(UUIDGenerator.generateRandomUUID(), "a#", false, null, false, false, + false, null); + _exchange.registerQueue(new Binding(null, "a.*.#.b.c.#.d",queue, _exchange, null)); + + int queueCount = routeMessage("a.c.b.b.c",0l); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + routeMessage("a.a.b.c.d",1l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 1l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testHashHash() throws AMQException + { + AMQQueue queue = _vhost.createQueue(UUIDGenerator.generateRandomUUID(), "a#", false, null, false, false, + false, null); + _exchange.registerQueue(new Binding(null, "a.#.*.#.d",queue, _exchange, null)); + + int queueCount = routeMessage("a.c.b.b.c",0l); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + routeMessage("a.a.b.c.d",1l); + + Assert.assertEquals(1, queue.getMessageCount()); + + Assert.assertEquals("Wrong message received", 1l, queue.getMessagesOnTheQueue().get(0).getMessage().getMessageNumber()); + + queue.deleteMessageFromTop(); + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testSubMatchFails() throws AMQException + { + AMQQueue queue = _vhost.createQueue(UUIDGenerator.generateRandomUUID(), "a", false, null, false, false, + false, null); + _exchange.registerQueue(new Binding(null, "a.b.c.d",queue, _exchange, null)); + + int queueCount = routeMessage("a.b.c",0l); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + } + + private int routeMessage(String routingKey, long messageNumber) throws AMQException + { + InboundMessage inboundMessage = mock(InboundMessage.class); + when(inboundMessage.getRoutingKey()).thenReturn(routingKey); + List queues = _exchange.route(inboundMessage); + ServerMessage message = mock(ServerMessage.class); + MessageReference ref = mock(MessageReference.class); + when(ref.getMessage()).thenReturn(message); + when(message.newReference()).thenReturn(ref); + when(message.getMessageNumber()).thenReturn(messageNumber); + for(BaseQueue q : queues) + { + q.enqueue(message); + } + + return queues.size(); + } + + public void testMoreRouting() throws AMQException + { + AMQQueue queue = _vhost.createQueue(UUIDGenerator.generateRandomUUID(), "a", false, null, false, false, + false, null); + _exchange.registerQueue(new Binding(null, "a.b",queue, _exchange, null)); + + + int queueCount = routeMessage("a.b.c",0l); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + } + + public void testMoreQueue() throws AMQException + { + AMQQueue queue = _vhost.createQueue(UUIDGenerator.generateRandomUUID(), "a", false, null, false, false, + false, null); + _exchange.registerQueue(new Binding(null, "a.b",queue, _exchange, null)); + + + int queueCount = routeMessage("a",0l); + Assert.assertEquals("Message should not route to any queues", 0, queueCount); + + Assert.assertEquals(0, queue.getMessageCount()); + + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/filter/JMSSelectorFilterTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/filter/JMSSelectorFilterTest.java new file mode 100644 index 0000000000..91002edfc6 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/filter/JMSSelectorFilterTest.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.filter; + +import junit.framework.TestCase; + +public class JMSSelectorFilterTest extends TestCase +{ + public void testEqualsAndHashCodeUsingSelectorString() throws Exception + { + final String selectorString = "1 = 1"; + + JMSSelectorFilter filter1 = new JMSSelectorFilter(new String(selectorString)); + JMSSelectorFilter filter2 = new JMSSelectorFilter(new String(selectorString)); + + assertEquals(filter1 + " should equal itself", filter1, filter1); + assertFalse(filter1 + " should not equal null", filter1.equals(null)); + assertEqualsAndHashcodeMatch(filter1, filter2); + + JMSSelectorFilter differentFilter = new JMSSelectorFilter("2 = 2"); + assertNotEqual(filter1, differentFilter); + } + + private void assertEqualsAndHashcodeMatch(JMSSelectorFilter filter1, JMSSelectorFilter filter2) + { + String message = filter1 + " and " + filter2 + " should be equal"; + + assertEquals(message, filter1, filter2); + assertEquals(message, filter2, filter1); + + assertEquals("Hashcodes of " + filter1 + " and " + filter2 + " should be equal", + filter1.hashCode(), filter2.hashCode()); + } + + private void assertNotEqual(JMSSelectorFilter filter, JMSSelectorFilter differentFilter) + { + assertFalse(filter.equals(differentFilter)); + assertFalse(differentFilter.equals(filter)); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/Log4jMessageLoggerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/Log4jMessageLoggerTest.java new file mode 100644 index 0000000000..e2a6a56ee2 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/Log4jMessageLoggerTest.java @@ -0,0 +1,271 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server.logging; + +import junit.framework.TestCase; +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; + +import org.apache.qpid.server.logging.actors.BrokerActor; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +/** Test that the Log4jMessageLogger defaults behave as expected */ +public class Log4jMessageLoggerTest extends TestCase +{ + private Level _rootLevel; + private Log4jTestAppender _appender; + + @Override + public void setUp() throws IOException + { + // Setup a file for logging + _appender = new Log4jTestAppender(); + + Logger root = Logger.getRootLogger(); + root.addAppender(_appender); + + _rootLevel = Logger.getRootLogger().getLevel(); + if (_rootLevel != Level.INFO) + { + root.setLevel(Level.INFO); + root.warn("Root Logger set to:" + _rootLevel + " Resetting to INFO for test."); + } + root.warn("Adding Test Appender:" + _appender); + } + + @Override + public void tearDown() + { + Logger root = Logger.getRootLogger(); + root.warn("Removing Test Appender:" + _appender); + root.warn("Resetting Root Level to : " + _rootLevel); + + Logger.getRootLogger().setLevel(_rootLevel); + + Logger.getRootLogger().removeAppender(_appender); + + //Call close on our appender. This will clear the log messages + // from Memory + _appender.close(); + } + + /** + * Verify that the Log4jMessageLogger successfully logs a message. + */ + public void testLoggedMessage() + { + Log4jMessageLogger msgLogger = new Log4jMessageLogger(); + assertTrue("Expected message logger to be enabled", msgLogger.isEnabled()); + + testLoggedMessage(msgLogger, true, getName()); + } + + /** + * Verify that for the given Log4jMessageLogger, after generating a message for the given + * log hierarchy that the outcome is as expected. + */ + private String testLoggedMessage(Log4jMessageLogger logger, boolean logExpected, String hierarchy) + { + //Create Message for test + String message = "testDefaults"; + + // Log the message + logger.rawMessage(message, hierarchy); + + if(logExpected) + { + verifyLogPresent(message); + } + else + { + verifyNoLog(message); + } + + return message; + } + + /** + * Test that specifying different log hierarchies to be used works as expected. + *

+ * Test this by using one hierarchy and verifying it succeeds, then disabling it and + * confirming this takes effect, and finally that using another hierarchy still succeeds. + */ + public void testMultipleHierarchyUsage() + { + String loggerName1 = getName() + ".TestLogger1"; + String loggerName2 = getName() + ".TestLogger2"; + + // Create a message logger to test + Log4jMessageLogger msgLogger = new Log4jMessageLogger(); + assertTrue("Expected message logger to be enabled", msgLogger.isEnabled()); + + //verify that using this hierarchy the message gets logged ok + String message = testLoggedMessage(msgLogger, true, loggerName1); + + //now disable that hierarchy in log4j + Logger.getLogger(loggerName1).setLevel(Level.OFF); + + //clear the previous message from the test appender + _appender.close(); + verifyNoLog(message); + + //verify that the hierarchy disabling took effect + testLoggedMessage(msgLogger, false, loggerName1); + + //now ensure that using a new hierarchy results in the message being output + testLoggedMessage(msgLogger, true, loggerName2); + } + + /** + * Test that log4j can be used to manipulate on a per-hierarchy(and thus message) basis + * whether a particular status message is enabled. + *

+ * Test this by using two hierarchies, setting one off and one on (info) via log4j directly, + * then confirming this gives the expected isMessageEnabled() result. Then reverse the log4j + * Levels for the Logger's and ensure the results change as expected. + */ + public void testEnablingAndDisablingMessages() + { + String loggerName1 = getName() + ".TestLogger1"; + String loggerName2 = getName() + ".TestLogger2"; + + Logger.getLogger(loggerName1).setLevel(Level.INFO); + Logger.getLogger(loggerName2).setLevel(Level.OFF); + + Log4jMessageLogger msgLogger = new Log4jMessageLogger(); + BrokerActor actor = new BrokerActor(msgLogger); + + assertTrue("Expected message logger to be enabled", msgLogger.isEnabled()); + + assertTrue("Message should be enabled", msgLogger.isMessageEnabled(actor, loggerName1)); + assertFalse("Message should be disabled", msgLogger.isMessageEnabled(actor, loggerName2)); + + Logger.getLogger(loggerName1).setLevel(Level.WARN); + Logger.getLogger(loggerName2).setLevel(Level.INFO); + + assertFalse("Message should be disabled", msgLogger.isMessageEnabled(actor, loggerName1)); + assertTrue("Message should be enabled", msgLogger.isMessageEnabled(actor, loggerName2)); + } + + /** + * Check that the Log Message reached log4j + * @param message the message to search for + */ + private void verifyLogPresent(String message) + { + List results = findMessageInLog(message); + + //Validate we only got one message + assertEquals("The result set was not as expected.", 1, results.size()); + + // Validate message + String line = results.get(0); + + assertNotNull("No Message retrieved from log file", line); + assertTrue("Message not contained in log.:" + line, + line.contains(message)); + } + + /** + * Check that the given Message is not present in the log4j records. + * @param message the message to search for + */ + private void verifyNoLog(String message) + { + List results = findMessageInLog(message); + + if (results.size() > 0) + { + System.err.println("Unexpected Log messages"); + + for (String msg : results) + { + System.err.println(msg); + } + } + + assertEquals("No message was expected.", 0, results.size()); + } + + /** + * Get the appenders list of events and return a list of all the messages + * that contain the given message + * + * @param message the search string + * @return The list of all logged messages that contain the search string. + */ + private List findMessageInLog(String message) + { + List log = _appender.getLog(); + + // Search Results for requested message + List result = new LinkedList(); + + for (LoggingEvent event : log) + { + if (String.valueOf(event.getMessage()).contains(message)) + { + result.add(String.valueOf(event.getMessage())); + } + } + + return result; + } + + + /** + * Log4j Appender that simply records all the Logging Events so we can + * verify that the above logging will make it to log4j in a unit test. + */ + private class Log4jTestAppender extends AppenderSkeleton + { + private List _log = new LinkedList(); + + protected void append(LoggingEvent loggingEvent) + { + _log.add(loggingEvent); + } + + public void close() + { + _log.clear(); + } + + /** + * @return the list of LoggingEvents that have occured in this Appender + */ + public List getLog() + { + return _log; + } + + public boolean requiresLayout() + { + return false; + } + } +} + diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/LogMessageTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/LogMessageTest.java new file mode 100644 index 0000000000..b0cb0ca0ab --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/LogMessageTest.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.logging; + +import junit.framework.TestCase; + +import org.apache.qpid.server.logging.messages.BrokerMessages; + +import java.util.Locale; +import java.util.ResourceBundle; + +public class LogMessageTest extends TestCase +{ + + /** + * Test that the US local has a loadable bundle. + * No longer have a specific en_US bundle so cannot verify that that version + * is loaded. Can only verify that we get a ResourceBundle loaded. + */ + public void testBundle() + { + Locale usLocal = Locale.US; + Locale.setDefault(usLocal); + ResourceBundle _messages = ResourceBundle.getBundle("org.apache.qpid.server.logging.messages.Broker_logmessages", + usLocal); + + assertNotNull("Unable to load ResourceBundle", _messages); + } + + /** + * Test that loading an undefined locale will result in loading of the + * default US locale. + */ + public void testUndefinedLocale() + { + Locale japanese = Locale.JAPANESE; + + Locale.setDefault(japanese); + try + { + ResourceBundle _messages = ResourceBundle.getBundle("org.apache.qpid.server.logging.messages.Broker_logmessages", + japanese); + + assertNotNull("Unable to load ResourceBundle", _messages); + + // If we attempt to load an undefined locale it should default to the Root locale. + assertEquals("Loaded bundle has incorrect locale.", Locale.ROOT, _messages.getLocale()); + } + catch (Throwable t) + { + fail(t.getMessage()); + } + } + + /** + * test Simultaneous log message generation. + * QPID-2137 highlighted that log message generation was not thread-safe. + * Test to ensure that simultaneous logging is possible and does not throw an exception. + * @throws InterruptedException if there is a problem joining logging threads. + */ + public void testSimultaneousLogging() throws InterruptedException + { + int LOGGERS = 10; + int LOG_COUNT = 10; + LogGenerator[] logGenerators = new LogGenerator[LOGGERS]; + Thread[] threads = new Thread[LOGGERS]; + + //Create Loggers + for (int i = 0; i < LOGGERS; i++) + { + logGenerators[i] = new LogGenerator(LOG_COUNT); + threads[i] = new Thread(logGenerators[i]); + } + + //Run Loggers + for (int i = 0; i < LOGGERS; i++) + { + threads[i].start(); + } + + //End Loggers + for (int i = 0; i < LOGGERS; i++) + { + threads[i].join(); + Exception e = logGenerators[i].getThrowException(); + // If we have an exception something went wrong. + // Check and see if it was QPID-2137 + if (e != null) + { + // Just log out if we find the usual exception causing QPID-2137 + if (e instanceof StringIndexOutOfBoundsException) + { + System.err.println("Detected QPID-2137"); + } + fail("Exception thrown during log generation:" + e); + } + } + } + + /** + * Inner class used by testSimultaneousLogging. + * + * This class creates a given number of LogMessages using the BrokerMessages package. + * CONFIG and LISTENING messages are both created per count. + * + * This class is run multiple times simultaneously so that we increase the chance of + * reproducing QPID-2137. This is reproduced when the pattern string used in the MessageFormat + * class is changed whilst formatting is taking place. + * + */ + class LogGenerator implements Runnable + { + private Exception _exception = null; + private int _count; + + /** + * @param count The number of Log Messages to generate + */ + LogGenerator(int count) + { + _count = count; + } + + public void run() + { + try + { + // try and generate _count iterations of Config & Listening messages. + for (int i = 0; i < _count; i++) + { + BrokerMessages.CONFIG("Config"); + BrokerMessages.LISTENING("TCP", 1234); + } + } + catch (Exception e) + { + // if something goes wrong recorded it for later analysis. + _exception = e; + } + } + + /** + * Return any exception that was thrown during the log generation. + * @return Exception + */ + public Exception getThrowException() + { + return _exception; + } + } + +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLogger.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLogger.java new file mode 100644 index 0000000000..be31f3d039 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLogger.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.logging; + + +import java.util.LinkedList; +import java.util.List; + +public class UnitTestMessageLogger extends AbstractRootMessageLogger +{ + private final List _log = new LinkedList(); + + public UnitTestMessageLogger() + { + + } + + public UnitTestMessageLogger(boolean statusUpdatesEnabled) + { + super(statusUpdatesEnabled); + } + + public void rawMessage(String message, String logHierarchy) + { + _log.add(message); + } + + public void rawMessage(String message, Throwable throwable, String logHierarchy) + { + _log.add(message); + + if(throwable != null) + { + _log.add(throwable); + } + } + + + public List getLogMessages() + { + return _log; + } + + public void clearLogMessages() + { + _log.clear(); + } + + public boolean messageContains(final int index, final String contains) + { + if (index + 1 > _log.size()) + { + throw new IllegalArgumentException("Message with index " + index + " has not been logged"); + } + final String message = _log.get(index).toString(); + return message.contains(contains); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLoggerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLoggerTest.java new file mode 100644 index 0000000000..e2e112be8f --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/UnitTestMessageLoggerTest.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.logging; + +import junit.framework.TestCase; + +import java.util.List; + +/** + * Test: UnitTestMessageLoggerTest + * + * This test verifies that UnitTestMessageLogger adheres to its interface. + * + * Messages are logged, and Throwables recorded in an array that can be + * retrieved and cleared. + * + */ +public class UnitTestMessageLoggerTest extends TestCase +{ + private static final String TEST_MESSAGE = "Test"; + private static final String TEST_THROWABLE = "Test Throwable"; + private static final String TEST_HIERARCHY = "test.hierarchy"; + + public void testRawMessage() + { + UnitTestMessageLogger logger = new UnitTestMessageLogger(); + + assertEquals("Messages logged before test start", 0, + logger.getLogMessages().size()); + + // Log a message + logger.rawMessage(TEST_MESSAGE, TEST_HIERARCHY); + + List messages = logger.getLogMessages(); + + assertEquals("Expected to have 1 messages logged", 1, messages.size()); + + assertEquals("First message not what was logged", + TEST_MESSAGE, messages.get(0)); + } + + public void testRawMessageWithThrowable() + { + UnitTestMessageLogger logger = new UnitTestMessageLogger(); + + assertEquals("Messages logged before test start", 0, + logger.getLogMessages().size()); + + // Log a message + Throwable throwable = new Throwable(TEST_THROWABLE); + + logger.rawMessage(TEST_MESSAGE, throwable, TEST_HIERARCHY); + + List messages = logger.getLogMessages(); + + assertEquals("Expected to have 2 entries", 2, messages.size()); + + assertEquals("Message text not what was logged", + TEST_MESSAGE, messages.get(0)); + + assertEquals("Message throwable not what was logged", + TEST_THROWABLE, ((Throwable) messages.get(1)).getMessage()); + + } + + public void testClear() + { + UnitTestMessageLogger logger = new UnitTestMessageLogger(); + + assertEquals("Messages logged before test start", 0, + logger.getLogMessages().size()); + + // Log a message + logger.rawMessage(TEST_MESSAGE, null, TEST_HIERARCHY); + + assertEquals("Expected to have 1 messages logged", + 1, logger.getLogMessages().size()); + + logger.clearLogMessages(); + + assertEquals("Expected to have no messagse after a clear", + 0, logger.getLogMessages().size()); + + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AMQPChannelActorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AMQPChannelActorTest.java new file mode 100644 index 0000000000..41b42fac78 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AMQPChannelActorTest.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.logging.actors; + +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.util.BrokerTestHelper; + +import java.util.List; + +/** + * Test : AMQPChannelActorTest + * Validate the AMQPChannelActor class. + * + * The test creates a new AMQPActor and then logs a message using it. + * + * The test then verifies that the logged message was the only one created and + * that the message contains the required message. + */ +public class AMQPChannelActorTest extends BaseConnectionActorTestCase +{ + + public void setUp() + { + // do nothing + } + + private void setUpNow() throws Exception + { + super.setUp(); + AMQSessionModel channel = BrokerTestHelper.createSession(1, getConnection()); + + setAmqpActor(new AMQPChannelActor(channel, getRootLogger())); + } + + + /** + * Test that when logging on behalf of the channel + * The test sends a message then verifies that it entered the logs. + * + * The log message should be fully repalaced (no '{n}' values) and should + * contain the channel id ('/ch:1') identification. + */ + public void testChannel() throws Exception + { + setUpNow(); + + final String message = sendTestLogMessage(getAmqpActor()); + + List logs = getRawLogger().getLogMessages(); + + assertEquals("Message log size not as expected.", 1, logs.size()); + + // Verify that the logged message is present in the output + assertTrue("Message was not found in log message:" + logs.get(0), + logs.get(0).toString().contains(message)); + + // Verify that the message has the correct type + assertTrue("Message contains the [con: prefix", + logs.get(0).toString().contains("[con:")); + + + // Verify that all the values were presented to the MessageFormatter + // so we will not end up with '{n}' entries in the log. + assertFalse("Verify that the string does not contain any '{'." + logs.get(0), + logs.get(0).toString().contains("{")); + + // Verify that the logged message contains the 'ch:1' marker + assertTrue("Message was not logged as part of channel 1" + logs.get(0), + logs.get(0).toString().contains("/ch:1")); + } + + /** + * Test that if logging is configured to be off via system property that + * no logging is presented + */ + public void testChannelLoggingOFF() throws Exception + { + setStatusUpdatesEnabled(false); + + setUpNow(); + + sendTestLogMessage(getAmqpActor()); + + List logs = getRawLogger().getLogMessages(); + + assertEquals("Message log size not as expected.", 0, logs.size()); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AMQPConnectionActorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AMQPConnectionActorTest.java new file mode 100644 index 0000000000..d1cf256563 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AMQPConnectionActorTest.java @@ -0,0 +1,122 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.actors; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; + +import java.util.List; + +/** + * Test : AMQPConnectionActorTest + * Validate the AMQPConnectionActor class. + * + * The test creates a new AMQPActor and then logs a message using it. + * + * The test then verifies that the logged message was the only one created and + * that the message contains the required message. + */ +public class AMQPConnectionActorTest extends BaseConnectionActorTestCase +{ + @Override + public void setUp() + { + //Prevent logger creation + } + + /** + * Test the AMQPActor logging as a Connection level. + * + * The test sends a message then verifies that it entered the logs. + * + * The log message should be fully repalaced (no '{n}' values) and should + * not contain any channel identification. + */ + public void testConnection() throws Exception + { + super.setUp(); + + final String message = sendLogMessage(); + + List logs = getRawLogger().getLogMessages(); + + assertEquals("Message log size not as expected.", 1, logs.size()); + + // Verify that the logged message is present in the output + assertTrue("Message was not found in log message", + logs.get(0).toString().contains(message)); + + // Verify that the message has the correct type + assertTrue("Message does not contain the [con: prefix", + logs.get(0).toString().contains("[con:")); + + // Verify that all the values were presented to the MessageFormatter + // so we will not end up with '{n}' entries in the log. + assertFalse("Verify that the string does not contain any '{'.", + logs.get(0).toString().contains("{")); + + // Verify that the logged message does not contains the 'ch:' marker + assertFalse("Message was logged with a channel identifier." + logs.get(0), + logs.get(0).toString().contains("/ch:")); + } + + public void testConnectionLoggingOff() throws Exception, AMQException + { + setStatusUpdatesEnabled(false); + + super.setUp(); + + sendLogMessage(); + + List logs = getRawLogger().getLogMessages(); + + assertEquals("Message log size not as expected.", 0, logs.size()); + + } + + private String sendLogMessage() + { + final String message = "test logging"; + + getAmqpActor().message(new LogSubject() + { + public String toLogString() + { + return "[AMQPActorTest]"; + } + + }, new LogMessage() + { + public String toString() + { + return message; + } + + public String getLogHierarchy() + { + return "test.hieracrchy"; + } + }); + return message; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AbstractManagementActorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AbstractManagementActorTest.java new file mode 100644 index 0000000000..bf38bb64bf --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/AbstractManagementActorTest.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.logging.actors; + +import java.security.Principal; +import java.security.PrivilegedAction; +import java.util.Collections; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.logging.NullRootMessageLogger; +import org.apache.qpid.server.security.auth.TestPrincipalUtils; +import org.apache.qpid.test.utils.QpidTestCase; + +public class AbstractManagementActorTest extends QpidTestCase +{ + private AbstractManagementActor _logActor; + + @Override + public void setUp() + { + _logActor = new AbstractManagementActor(new NullRootMessageLogger(), AbstractManagementActor.UNKNOWN_PRINCIPAL) + { + @Override + public String getLogMessage() + { + return null; + } + }; + } + + public void testGetPrincipalName() + { + Subject subject = TestPrincipalUtils.createTestSubject("guest"); + + final String principalName = Subject.doAs(subject, + new PrivilegedAction() + { + public String run() + { + return _logActor.getPrincipalName(); + } + }); + + assertEquals("guest", principalName); + } + + public void testGetPrincipalNameUsingSubjectWithoutAuthenticatedPrincipal() + { + Subject subject = new Subject(true, Collections.emptySet(), Collections.emptySet(), Collections.emptySet()); + + final String principalName = Subject.doAs(subject, + new PrivilegedAction() + { + public String run() + { + return _logActor.getPrincipalName(); + } + }); + + assertEquals(AbstractManagementActor.UNKNOWN_PRINCIPAL, principalName); + } + + public void testGetPrincipalWithoutSubject() + { + assertEquals(AbstractManagementActor.UNKNOWN_PRINCIPAL, _logActor.getPrincipalName()); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/BaseActorTestCase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/BaseActorTestCase.java new file mode 100644 index 0000000000..30c3a51604 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/BaseActorTestCase.java @@ -0,0 +1,120 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.actors; + +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.UnitTestMessageLogger; +import org.apache.qpid.test.utils.QpidTestCase; + +public class BaseActorTestCase extends QpidTestCase +{ + private boolean _statusUpdatesEnabled = true; + private LogActor _amqpActor; + private UnitTestMessageLogger _rawLogger; + private RootMessageLogger _rootLogger; + + @Override + public void setUp() throws Exception + { + super.setUp(); + CurrentActor.removeAll(); + CurrentActor.setDefault(null); + _rawLogger = new UnitTestMessageLogger(_statusUpdatesEnabled); + _rootLogger = _rawLogger; + } + + @Override + public void tearDown() throws Exception + { + if(_rawLogger != null) + { + _rawLogger.clearLogMessages(); + } + CurrentActor.removeAll(); + CurrentActor.setDefault(null); + super.tearDown(); + } + + public String sendTestLogMessage(LogActor actor) + { + String message = "Test logging: " + getName(); + sendTestLogMessage(actor, message); + + return message; + } + + public void sendTestLogMessage(LogActor actor, final String message) + { + actor.message(new LogSubject() + { + public String toLogString() + { + return message; + } + + }, new LogMessage() + { + public String toString() + { + return message; + } + + public String getLogHierarchy() + { + return "test.hierarchy"; + } + }); + } + + public boolean isStatusUpdatesEnabled() + { + return _statusUpdatesEnabled; + } + + public void setStatusUpdatesEnabled(boolean statusUpdatesEnabled) + { + _statusUpdatesEnabled = statusUpdatesEnabled; + } + + public LogActor getAmqpActor() + { + return _amqpActor; + } + + public void setAmqpActor(LogActor amqpActor) + { + _amqpActor = amqpActor; + } + + public UnitTestMessageLogger getRawLogger() + { + return _rawLogger; + } + + public RootMessageLogger getRootLogger() + { + return _rootLogger; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/BaseConnectionActorTestCase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/BaseConnectionActorTestCase.java new file mode 100644 index 0000000000..1cb6474e41 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/BaseConnectionActorTestCase.java @@ -0,0 +1,74 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.actors; + +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class BaseConnectionActorTestCase extends BaseActorTestCase +{ + private AMQConnectionModel _session; + private VirtualHost _virtualHost; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _session = BrokerTestHelper.createConnection(); + _virtualHost = BrokerTestHelper.createVirtualHost("test"); + setAmqpActor(new AMQPConnectionActor(_session, getRootLogger())); + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + @Override + public void tearDown() throws Exception + { + try + { + if(_virtualHost != null) + { + _virtualHost.close(); + } + if (_session != null) + { + _session.close(AMQConstant.CONNECTION_FORCED, ""); + } + } + finally + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + } + + public AMQConnectionModel getConnection() + { + return _session; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/CurrentActorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/CurrentActorTest.java new file mode 100644 index 0000000000..701ccaab47 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/CurrentActorTest.java @@ -0,0 +1,253 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.actors; + +import org.apache.commons.configuration.ConfigurationException; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.NullRootMessageLogger; +import org.apache.qpid.server.util.BrokerTestHelper; + +/** + * Test : CurrentActorTest + * Summary: + * Validate ThreadLocal operation. + * + * Test creates THREADS number of threads which all then execute the same test + * together ( as close as looping Thread.start() will allow). + * + * Test: + * Test sets the CurrentActor then proceeds to retrieve the value and use it. + * + * The test also validates that it is the same LogActor that this thread set. + * + * Finally the LogActor is removed and tested to make sure that it was + * successfully removed. + * + * By having a higher number of threads than would normally be used in the + * Poolling filter we aim to catch the race condition where a ThreadLocal remove + * is called before one or more threads call get(). This way we can ensure that + * the remove does not affect more than the Thread it was called in. + */ +public class CurrentActorTest extends BaseConnectionActorTestCase +{ + //Set this to be a reasonably large number + private static final int THREADS = 10; + + /** + * Test that CurrentActor behaves as LIFO queue. + * + * Test creates two Actors Connection and Channel and then sets the + * CurrentActor. + * + * The test validates that CurrentActor remembers the Connection actor + * after the Channel actor has been removed. + * + * And then finally validates that removing the Connection actor results + * in there being no actors set. + * + * @throws AMQException + * @throws org.apache.commons.configuration.ConfigurationException + */ + public void testLIFO() throws AMQException, ConfigurationException + { + assertTrue("Unexpected actor: " + CurrentActor.get(), CurrentActor.get() instanceof TestLogActor); + AMQPConnectionActor connectionActor = new AMQPConnectionActor(getConnection(), + new NullRootMessageLogger()); + + /* + * Push the actor on to the stack: + * + * CurrentActor -> Connection + * Stack -> null + */ + CurrentActor.set(connectionActor); + + //Use the Actor to send a simple message + sendTestLogMessage(CurrentActor.get()); + + // Verify it was the same actor as we set earlier + assertEquals("Retrieved actor is not as expected ", + connectionActor, CurrentActor.get()); + + /** + * Set the actor to now be the Channel actor so testing the ability + * to push the actor on to the stack: + * + * CurrentActor -> Channel + * Stack -> Connection, null + * + */ + + AMQSessionModel channel = BrokerTestHelper.createSession(1, getConnection()); + + AMQPChannelActor channelActor = new AMQPChannelActor(channel, + new NullRootMessageLogger()); + + CurrentActor.set(channelActor); + + //Use the Actor to send a simple message + sendTestLogMessage(CurrentActor.get()); + + // Verify it was the same actor as we set earlier + assertEquals("Retrieved actor is not as expected ", + channelActor, CurrentActor.get()); + + // Remove the ChannelActor from the stack + CurrentActor.remove(); + /* + * Pop the actor on to the stack: + * + * CurrentActor -> Connection + * Stack -> null + */ + + + // Verify we now have the same connection actor as we set earlier + assertEquals("Retrieved actor is not as expected ", + connectionActor, CurrentActor.get()); + + // Verify that removing the our last actor it returns us to the test + // default that the ApplicationRegistry sets. + CurrentActor.remove(); + /* + * Pop the actor on to the stack: + * + * CurrentActor -> null + */ + + + assertEquals("CurrentActor not the Test default", TestLogActor.class ,CurrentActor.get().getClass()); + } + + /** + * Test the setting CurrentActor is done correctly as a ThreadLocal. + * + * The test starts 'THREADS' threads that all set the CurrentActor log + * a message then remove the actor. + * + * Checks are done to ensure that there is no set actor after the remove. + * + * If the ThreadLocal was not working then having concurrent actor sets + * would result in more than one actor and so the remove will not result + * in the clearing of the CurrentActor + * + */ + public void testThreadLocal() + { + + // Setup the threads + LogMessagesWithAConnectionActor[] threads = new LogMessagesWithAConnectionActor[THREADS]; + for (int count = 0; count < THREADS; count++) + { + threads[count] = new LogMessagesWithAConnectionActor(); + } + + //Run the threads + for (int count = 0; count < THREADS; count++) + { + threads[count].start(); + } + + // Wait for them to finish + for (int count = 0; count < THREADS; count++) + { + try + { + threads[count].join(); + } + catch (InterruptedException e) + { + //if we are interrupted then we will exit shortly. + } + } + + // Verify that none of the tests threw an exception + for (int count = 0; count < THREADS; count++) + { + if (threads[count].getException() != null) + { + threads[count].getException().printStackTrace(); + fail("Error occured in thread:" + count + "("+threads[count].getException()+")"); + } + } + } + + /** + * Creates a new ConnectionActor and logs the given number of messages + * before removing the actor and validating that there is no set actor. + */ + public class LogMessagesWithAConnectionActor extends Thread + { + private Throwable _exception; + + public LogMessagesWithAConnectionActor() + { + } + + public void run() + { + + // Create a new actor using retrieving the rootMessageLogger from + // the default ApplicationRegistry. + //fixme reminder that we need a better approach for broker testing. + try + { + LogActor defaultActor = CurrentActor.get(); + + AMQPConnectionActor actor = new AMQPConnectionActor(getConnection(), + new NullRootMessageLogger()); + + CurrentActor.set(actor); + + //Use the Actor to send a simple message + sendTestLogMessage(CurrentActor.get()); + + // Verify it was the same actor as we set earlier + if(!actor.equals(CurrentActor.get())) + { + throw new IllegalArgumentException("Retrieved actor is not as expected "); + } + + // Verify that removing the actor works for this thread + CurrentActor.remove(); + + if(CurrentActor.get() != defaultActor) + { + throw new IllegalArgumentException("CurrentActor ("+CurrentActor.get()+") should be default actor" + defaultActor); + } + } + catch (Throwable e) + { + _exception = e; + } + + } + + public Throwable getException() + { + return _exception; + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/HttpManagementActorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/HttpManagementActorTest.java new file mode 100644 index 0000000000..905de4b639 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/HttpManagementActorTest.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.server.logging.actors; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.TestPrincipalUtils; + +import java.security.PrivilegedAction; +import java.util.List; + +public class HttpManagementActorTest extends BaseActorTestCase +{ + private static final String IP = "127.0.0.1"; + private static final int PORT = 1; + private static final String SUFFIX = "(" + IP + ":" + PORT + ")] "; + + @Override + public void setUp() throws Exception + { + super.setUp(); + setAmqpActor(new HttpManagementActor(getRootLogger(), IP, PORT)); + } + + public void testSubjectPrincipalNameAppearance() + { + Subject subject = TestPrincipalUtils.createTestSubject("guest"); + + final String message = Subject.doAs(subject, new PrivilegedAction() + { + public String run() + { + return sendTestLogMessage(getAmqpActor()); + } + }); + + assertNotNull("Test log message is not created!", message); + + List logs = getRawLogger().getLogMessages(); + assertEquals("Message log size not as expected.", 1, logs.size()); + + String logMessage = logs.get(0).toString(); + assertTrue("Message was not found in log message", logMessage.contains(message)); + assertTrue("Message does not contain expected value: " + logMessage, logMessage.contains("[mng:guest" + SUFFIX)); + } + + /** It's necessary to test successive calls because HttpManagementActor caches + * its log message based on principal name */ + public void testGetLogMessageCaching() + { + assertLogMessageWithoutPrincipal(); + assertLogMessageWithPrincipal("my_principal"); + assertLogMessageWithPrincipal("my_principal2"); + assertLogMessageWithoutPrincipal(); + } + + private void assertLogMessageWithoutPrincipal() + { + String message = getAmqpActor().getLogMessage(); + assertEquals("Unexpected log message", "[mng:" + AbstractManagementActor.UNKNOWN_PRINCIPAL + SUFFIX, message); + } + + private void assertLogMessageWithPrincipal(String principalName) + { + Subject subject = TestPrincipalUtils.createTestSubject(principalName); + final String message = Subject.doAs(subject, new PrivilegedAction() + { + public String run() + { + return getAmqpActor().getLogMessage(); + } + }); + + assertEquals("Unexpected log message", "[mng:" + principalName + SUFFIX, message); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java new file mode 100644 index 0000000000..a0bfa592db --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/ManagementActorTest.java @@ -0,0 +1,186 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.actors; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.TestPrincipalUtils; + +import java.security.PrivilegedAction; +import java.util.List; + +public class ManagementActorTest extends BaseActorTestCase +{ + + private static final String IP = "127.0.0.1"; + private static final String CONNECTION_ID = "1"; + private String _threadName; + + @Override + public void setUp() throws Exception + { + super.setUp(); + setAmqpActor(new ManagementActor(getRootLogger())); + + // Set the thread name to be the same as a RMI JMX Connection would use + _threadName = Thread.currentThread().getName(); + Thread.currentThread().setName("RMI TCP Connection(" + CONNECTION_ID + ")-" + IP); + } + + @Override + public void tearDown() throws Exception + { + Thread.currentThread().setName(_threadName); + super.tearDown(); + } + + /** + * Test the AMQPActor logging as a Connection level. + * + * The test sends a message then verifies that it entered the logs. + * + * The log message should be fully replaced (no '{n}' values) and should + * not contain any channel identification. + */ + public void testConnection() + { + final String message = sendTestLogMessage(getAmqpActor()); + + List logs = getRawLogger().getLogMessages(); + + assertEquals("Message log size not as expected.", 1, logs.size()); + + // Verify that the logged message is present in the output + assertTrue("Message was not found in log message", + logs.get(0).toString().contains(message)); + + // Verify that all the values were presented to the MessageFormatter + // so we will not end up with '{n}' entries in the log. + assertFalse("Verify that the string does not contain any '{'.", + logs.get(0).toString().contains("{")); + + // Verify that the message has the correct type + assertTrue("Message does not contain the [mng: prefix", + logs.get(0).toString().contains("[mng:")); + + // Verify that the logged message does not contains the 'ch:' marker + assertFalse("Message was logged with a channel identifier." + logs.get(0), + logs.get(0).toString().contains("/ch:")); + + // Verify that the message has the right values + assertTrue("Message contains the [mng: prefix", + logs.get(0).toString().contains("[mng:N/A(" + IP + ")")); + } + + /** + * Tests appearance of principal name in log message + */ + public void testSubjectPrincipalNameAppearance() + { + Subject subject = TestPrincipalUtils.createTestSubject("guest"); + + final String message = Subject.doAs(subject, new PrivilegedAction() + { + public String run() + { + return sendTestLogMessage(getAmqpActor()); + } + }); + + // Verify that the log message was created + assertNotNull("Test log message is not created!", message); + + List logs = getRawLogger().getLogMessages(); + + // Verify that at least one log message was added to log + assertEquals("Message log size not as expected.", 1, logs.size()); + + String logMessage = logs.get(0).toString(); + + // Verify that the logged message is present in the output + assertTrue("Message was not found in log message", logMessage.contains(message)); + + // Verify that the message has the right principal value + assertTrue("Message contains the [mng: prefix", logMessage.contains("[mng:guest(" + IP + ")")); + } + + public void testGetLogMessageWithSubject() + { + assertLogMessageInRMIThreadWithPrincipal("RMI TCP Connection(" + CONNECTION_ID + ")-" + IP, "my_principal"); + } + + public void testGetLogMessageWithoutSubjectButWithActorPrincipal() + { + String principalName = "my_principal"; + setAmqpActor(new ManagementActor(getRootLogger(), principalName)); + String message = getAmqpActor().getLogMessage(); + assertEquals("Unexpected log message", "[mng:" + principalName + "(" + IP + ")] ", message); + } + + /** It's necessary to test successive calls because ManagementActor caches its log message based on thread and principal name */ + public void testGetLogMessageCaching() + { + String originalThreadName = "RMI TCP Connection(1)-" + IP; + assertLogMessageInRMIThreadWithoutPrincipal(originalThreadName); + assertLogMessageInRMIThreadWithPrincipal(originalThreadName, "my_principal"); + assertLogMessageInRMIThreadWithPrincipal("RMI TCP Connection(2)-" + IP, "my_principal"); + } + + public void testGetLogMessageAfterRemovingSubject() + { + assertLogMessageInRMIThreadWithPrincipal("RMI TCP Connection(1)-" + IP, "my_principal"); + + Thread.currentThread().setName("RMI TCP Connection(2)-" + IP ); + String message = getAmqpActor().getLogMessage(); + assertEquals("Unexpected log message", "[mng:N/A(" + IP + ")] ", message); + + assertLogMessageWithoutPrincipal("TEST"); + } + + private void assertLogMessageInRMIThreadWithoutPrincipal(String threadName) + { + Thread.currentThread().setName(threadName ); + String message = getAmqpActor().getLogMessage(); + assertEquals("Unexpected log message", "[mng:N/A(" + IP + ")] ", message); + } + + private void assertLogMessageWithoutPrincipal(String threadName) + { + Thread.currentThread().setName(threadName ); + String message = getAmqpActor().getLogMessage(); + assertEquals("Unexpected log message", "[" + threadName +"] ", message); + } + + private void assertLogMessageInRMIThreadWithPrincipal(String threadName, String principalName) + { + Thread.currentThread().setName(threadName); + Subject subject = TestPrincipalUtils.createTestSubject(principalName); + final String message = Subject.doAs(subject, new PrivilegedAction() + { + public String run() + { + return getAmqpActor().getLogMessage(); + } + }); + + assertEquals("Unexpected log message", "[mng:" + principalName + "(" + IP + ")] ", message); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/QueueActorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/QueueActorTest.java new file mode 100644 index 0000000000..55153b7389 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/QueueActorTest.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.logging.actors; + +import java.util.List; + +import org.apache.qpid.server.util.BrokerTestHelper; + +public class QueueActorTest extends BaseConnectionActorTestCase +{ + + @Override + public void setUp() throws Exception + { + super.setUp(); + setAmqpActor(new QueueActor(BrokerTestHelper.createQueue(getName(), getVirtualHost()), getRootLogger())); + } + + /** + * Test the QueueActor as a logger. + * + * The test logs a message then verifies that it entered the logs correctly + * + * The log message should be fully repalaced (no '{n}' values) and should + * contain the correct queue identification. + */ + public void testQueueActor() + { + final String message = sendTestLogMessage(getAmqpActor()); + + List logs = getRawLogger().getLogMessages(); + + assertEquals("Message log size not as expected.", 1, logs.size()); + + String log = logs.get(0).toString(); + + // Verify that the logged message is present in the output + assertTrue("Message was not found in log message", + log.contains(message)); + + // Verify that all the values were presented to the MessageFormatter + // so we will not end up with '{n}' entries in the log. + assertFalse("Verify that the string does not contain any '{':" + log, + log.contains("{")); + + // Verify that the message has the correct type + assertTrue("Message contains the [vh: prefix:" + log, + log.contains("[vh(")); + + // Verify that the logged message contains the 'qu(' marker + String expected = "qu(" + getName() + ")"; + assertTrue("Message was not logged with a queue identifer '"+expected+"' actual:" + log, + log.contains(expected)); + } + +} + diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/SubscriptionActorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/SubscriptionActorTest.java new file mode 100644 index 0000000000..92915e7092 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/SubscriptionActorTest.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.logging.actors; + +import org.apache.qpid.server.subscription.MockSubscription; +import org.apache.qpid.server.util.BrokerTestHelper; + +import java.util.List; + +/** + * Test : AMQPConnectionActorTest + * Validate the AMQPConnectionActor class. + * + * The test creates a new AMQPActor and then logs a message using it. + * + * The test then verifies that the logged message was the only one created and + * that the message contains the required message. + */ +public class SubscriptionActorTest extends BaseConnectionActorTestCase +{ + + @Override + public void setUp() throws Exception + { + super.setUp(); + + MockSubscription mockSubscription = new MockSubscription(); + + mockSubscription.setQueue(BrokerTestHelper.createQueue(getName(), getVirtualHost()), false); + + setAmqpActor(new SubscriptionActor(getRootLogger(), mockSubscription)); + } + + /** + * Test the AMQPActor logging as a Subscription logger. + * + * The test sends a message then verifies that it entered the logs. + * + * The log message should be fully repalaced (no '{n}' values) and should + * contain subscription identification. + */ + public void testSubscription() + { + final String message = sendTestLogMessage(getAmqpActor()); + + List logs = getRawLogger().getLogMessages(); + + assertEquals("Message log size not as expected.", 1, logs.size()); + + // Verify that the logged message is present in the output + assertTrue("Message was not found in log message", + logs.get(0).toString().contains(message)); + + // Verify that all the values were presented to the MessageFormatter + // so we will not end up with '{n}' entries in the log. + assertFalse("Verify that the string does not contain any '{'.", + logs.get(0).toString().contains("{")); + + // Verify that the message has the correct type + assertTrue("Message contains the [sub: prefix", + logs.get(0).toString().contains("[sub:")); + + // Verify that the logged message does not contains the 'ch:' marker + assertFalse("Message was logged with a channel identifier." + logs.get(0), + logs.get(0).toString().contains("/ch:")); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/TestLogActor.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/TestLogActor.java new file mode 100644 index 0000000000..30f4e16e42 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/actors/TestLogActor.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.logging.actors; + +import org.apache.qpid.server.logging.RootMessageLogger; + +public class TestLogActor extends AbstractActor +{ + public TestLogActor(RootMessageLogger rootLogger) + { + super(rootLogger); + } + + public String getLogMessage() + { + return "[Test Actor] "; + } +} + \ No newline at end of file diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/log4j/LoggingFacadeTest.log4j.xml b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/log4j/LoggingFacadeTest.log4j.xml new file mode 100644 index 0000000000..62ec877d3d --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/log4j/LoggingFacadeTest.log4j.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/log4j/LoggingManagementFacadeTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/log4j/LoggingManagementFacadeTest.java new file mode 100644 index 0000000000..72b34868ba --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/log4j/LoggingManagementFacadeTest.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.log4j; + +import java.io.File; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Level; +import org.apache.qpid.test.utils.TestFileUtils; +import org.apache.qpid.util.FileUtils; + +import junit.framework.TestCase; + +public class LoggingManagementFacadeTest extends TestCase +{ + private LoggingManagementFacade _loggingFacade; + private String _log4jXmlFile; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _log4jXmlFile = createTestLog4jXml(); + _loggingFacade = LoggingManagementFacade.configure(_log4jXmlFile); + } + + public void testGetAvailableLoggerLevels() throws Exception + { + List levels = _loggingFacade.getAvailableLoggerLevels(); + assertTrue(levels.contains("ALL")); + assertTrue(levels.contains("TRACE")); + assertTrue(levels.contains("DEBUG")); + assertTrue(levels.contains("INFO")); + assertTrue(levels.contains("WARN")); + assertTrue(levels.contains("ERROR")); + assertTrue(levels.contains("FATAL")); + assertTrue(levels.contains("OFF")); + assertEquals(8, levels.size()); + } + + public void testRetrieveConfigFileRootLoggerLevel() throws Exception + { + String level = _loggingFacade.retrieveConfigFileRootLoggerLevel(); + assertEquals(Level.WARN.toString(), level); + } + + public void testSetConfigFileRootLoggerLevel() throws Exception + { + String oldLevel = _loggingFacade.retrieveConfigFileRootLoggerLevel(); + assertEquals("WARN", oldLevel); + + _loggingFacade.setConfigFileRootLoggerLevel("INFO"); + + String level = _loggingFacade.retrieveConfigFileRootLoggerLevel(); + assertEquals("INFO", level); + } + + public void testRetrieveConfigFileLoggerLevels() throws Exception + { + Map levels = _loggingFacade.retrieveConfigFileLoggersLevels(); + assertEquals(3, levels.size()); + String abcLevel = levels.get("a.b.c"); + String abc1Level = levels.get("a.b.c.1"); + String abc2Level = levels.get("a.b.c.2"); + assertEquals("INFO", abcLevel); + assertEquals("DEBUG", abc1Level); + assertEquals("TRACE", abc2Level); + } + + public void testSetConfigFileLoggerLevels() throws Exception + { + final String loggerName = "a.b.c"; + + assertConfigFileLoggingLevel(loggerName, "INFO"); + + _loggingFacade.setConfigFileLoggerLevel(loggerName, "WARN"); + + Map levels = _loggingFacade.retrieveConfigFileLoggersLevels(); + String abcLevel = levels.get(loggerName); + assertEquals("WARN", abcLevel); + } + + public void testSetConfigFileLoggerLevelsWhereLoggerDoesNotExist() throws Exception + { + try + { + _loggingFacade.setConfigFileLoggerLevel("does.not.exist", "WARN"); + fail("Exception not thrown"); + } + catch (LoggingFacadeException lfe) + { + // PASS + assertEquals("Can't find logger does.not.exist", lfe.getMessage()); + } + } + + public void testRetrieveRuntimeRootLoggerLevel() throws Exception + { + String level = _loggingFacade.retrieveRuntimeRootLoggerLevel(); + assertEquals(Level.WARN.toString(), level); + } + + public void testSetRuntimeRootLoggerLevel() throws Exception + { + String oldLevel = _loggingFacade.retrieveRuntimeRootLoggerLevel(); + assertEquals("WARN", oldLevel); + + _loggingFacade.setRuntimeRootLoggerLevel("INFO"); + + String level = _loggingFacade.retrieveRuntimeRootLoggerLevel(); + assertEquals("INFO", level); + } + + public void testRetrieveRuntimeLoggersLevels() throws Exception + { + Map levels = _loggingFacade.retrieveRuntimeLoggersLevels(); + // Don't assert size as implementation itself uses logging and we'd count its loggers too + String abcLevel = levels.get("a.b.c"); + String abc1Level = levels.get("a.b.c.1"); + String abc2Level = levels.get("a.b.c.2"); + assertEquals("INFO", abcLevel); + assertEquals("DEBUG", abc1Level); + assertEquals("TRACE", abc2Level); + } + + public void testSetRuntimeLoggerLevel() throws Exception + { + final String loggerName = "a.b.c"; + + assertRuntimeLoggingLevel(loggerName, "INFO"); + + _loggingFacade.setRuntimeLoggerLevel(loggerName, "WARN"); + + assertRuntimeLoggingLevel(loggerName, "WARN"); + } + + public void testSetRuntimeLoggerToInheritFromParent() throws Exception + { + final String parentLoggerName = "a.b.c"; + final String childLoggerName = "a.b.c.1"; + + assertRuntimeLoggingLevel(parentLoggerName, "INFO"); + assertRuntimeLoggingLevel(childLoggerName, "DEBUG"); + + _loggingFacade.setRuntimeLoggerLevel(childLoggerName, null); + + assertRuntimeLoggingLevel(parentLoggerName, "INFO"); + assertRuntimeLoggingLevel(childLoggerName, "INFO"); + } + + public void testSetRuntimeLoggerLevelsWhereLoggerDoesNotExist() throws Exception + { + final String loggerName = "does.not.exist2"; + + Map oldLevels = _loggingFacade.retrieveRuntimeLoggersLevels(); + assertFalse(oldLevels.containsKey(loggerName)); + + try + { + _loggingFacade.setRuntimeLoggerLevel(loggerName, "WARN"); + fail("Exception not thrown"); + } + catch (LoggingFacadeException lfe) + { + // PASS + assertEquals("Can't find logger " + loggerName, lfe.getMessage()); + } + + Map levels = _loggingFacade.retrieveRuntimeLoggersLevels(); + assertFalse(levels.containsKey(loggerName)); + } + + public void testReloadOfChangedLog4JFileUpdatesRuntimeLogLevel() throws Exception + { + final String loggerName = "a.b.c"; + + assertRuntimeLoggingLevel(loggerName, "INFO"); + assertConfigFileLoggingLevel(loggerName, "INFO"); + + _loggingFacade.setConfigFileLoggerLevel(loggerName, "WARN"); + + assertRuntimeLoggingLevel(loggerName, "INFO"); + + _loggingFacade.reload(); + + assertRuntimeLoggingLevel(loggerName, "WARN"); + } + + + public void testReloadOfLog4JFileRevertsRuntimeChanges() throws Exception + { + final String loggerName = "a.b.c"; + + assertRuntimeLoggingLevel(loggerName, "INFO"); + assertConfigFileLoggingLevel(loggerName, "INFO"); + + _loggingFacade.setRuntimeLoggerLevel(loggerName, "WARN"); + + assertRuntimeLoggingLevel(loggerName, "WARN"); + + _loggingFacade.reload(); + + assertRuntimeLoggingLevel(loggerName, "INFO"); + } + + private void assertConfigFileLoggingLevel(final String loggerName, String expectedLevel) throws Exception + { + Map levels = _loggingFacade.retrieveConfigFileLoggersLevels(); + String actualLevel = levels.get(loggerName); + assertEquals(expectedLevel, actualLevel); + } + + private void assertRuntimeLoggingLevel(final String loggerName, String expectedLevel) throws Exception + { + Map levels = _loggingFacade.retrieveRuntimeLoggersLevels(); + String actualLevel = levels.get(loggerName); + assertEquals(expectedLevel, actualLevel); + } + + private String createTestLog4jXml() throws Exception + { + return TestFileUtils.createTempFileFromResource(this, "LoggingFacadeTest.log4j.xml").getAbsolutePath(); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/AbstractTestMessages.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/AbstractTestMessages.java new file mode 100644 index 0000000000..229d75c69f --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/AbstractTestMessages.java @@ -0,0 +1,128 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.messages; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.PropertiesConfiguration; + +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.UnitTestMessageLogger; +import org.apache.qpid.server.logging.actors.TestLogActor; +import org.apache.qpid.server.logging.subjects.TestBlankSubject; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.test.utils.QpidTestCase; + +import java.util.List; + +public abstract class AbstractTestMessages extends QpidTestCase +{ + protected Configuration _config = new PropertiesConfiguration(); + protected LogMessage _logMessage = null; + protected LogActor _actor; + protected UnitTestMessageLogger _logger; + protected LogSubject _logSubject = new TestBlankSubject(); + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _logger = new UnitTestMessageLogger(); + + _actor = new TestLogActor(_logger); + BrokerTestHelper.setUp(); + } + + @Override + public void tearDown() throws Exception + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + + protected List performLog() + { + if (_logMessage == null) + { + throw new NullPointerException("LogMessage has not been set"); + } + + _actor.message(_logSubject, _logMessage); + + return _logger.getLogMessages(); + } + + protected void validateLogMessage(List logs, String tag, String[] expected) + { + validateLogMessage(logs, tag, false, expected); + } + + /** + * Validate that only a single log message occurred and that the message + * section starts with the specified tag + * + * @param logs the logs generated during test run + * @param tag the tag to check for + * @param expected the expected log messages + * @param useStringForNull replace a null String reference with "null" + */ + protected void validateLogMessage(List logs, String tag, boolean useStringForNull, String[] expected) + { + assertEquals("Log has incorrect message count", 1, logs.size()); + + //We trim() here as we don't care about extra white space at the end of the log message + // but we do care about the ability to easily check we don't have unexpected text at + // the end. + String log = String.valueOf(logs.get(0)).trim(); + + // Simple switch to print out all the logged messages + //System.err.println(log); + + int msgIndex = log.indexOf(_logSubject.toLogString())+_logSubject.toLogString().length(); + + assertTrue("Unable to locate Subject:" + log, msgIndex != -1); + + String message = log.substring(msgIndex); + + assertTrue("Message does not start with tag:" + tag + ":" + message, + message.startsWith(tag)); + + // Test that the expected items occur in order. + int index = 0; + for (String text : expected) + { + if(useStringForNull && text == null) + { + text = "null"; + } + index = message.indexOf(text, index); + assertTrue("Message does not contain expected (" + text + ") text :" + message, index != -1); + index = index + text.length(); + } + + //Check there is nothing left on the log message + assertEquals("Message has more text. '" + log.substring(msgIndex + index) + "'", + log.length(), msgIndex + index); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/BindingMessagesTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/BindingMessagesTest.java new file mode 100644 index 0000000000..22de8349c6 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/BindingMessagesTest.java @@ -0,0 +1,64 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.messages; + +import java.util.List; + +/** + * Test BND Log Messages + */ +public class BindingMessagesTest extends AbstractTestMessages +{ + + public void testBindCreate_NoArgs() + { + _logMessage = BindingMessages.CREATED(null, false); + List log = performLog(); + + String[] expected = {"Create"}; + + validateLogMessage(log, "BND-1001", expected); + } + + public void testBindCreate_Args() + { + String arguments = "arguments"; + + _logMessage = BindingMessages.CREATED(arguments, true); + List log = performLog(); + + String[] expected = {"Create", ": Arguments :", arguments}; + + validateLogMessage(log, "BND-1001", expected); + } + + public void testBindDelete() + { + _logMessage = BindingMessages.DELETED(); + + List log = performLog(); + + String[] expected = {"Deleted"}; + + validateLogMessage(log, "BND-1002", expected); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/BrokerMessagesTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/BrokerMessagesTest.java new file mode 100644 index 0000000000..6571d20711 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/BrokerMessagesTest.java @@ -0,0 +1,146 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.messages; + +import java.text.NumberFormat; +import java.util.List; + +/** + * Test BRK log Messages + */ +public class BrokerMessagesTest extends AbstractTestMessages +{ + public void testBrokerStartup() + { + String version = "Qpid 0.6"; + String build = "796936M"; + + _logMessage = BrokerMessages.STARTUP(version, build); + List log = performLog(); + + String[] expected = {"Startup :", "Version:", version, "Build:", build}; + + validateLogMessage(log, "BRK-1001", expected); + } + + public void testBrokerListening() + { + String transport = "TCP"; + Integer port = 2765; + + _logMessage = BrokerMessages.LISTENING(transport, port); + + List log = performLog(); + + String[] expected = {"Starting", "Listening on ", + transport, "port ", String.valueOf(port)}; + + validateLogMessage(log, "BRK-1002", expected); + } + + public void testBrokerShuttingDown() + { + String transport = "TCP"; + Integer port = 2765; + + _logMessage = BrokerMessages.SHUTTING_DOWN(transport, port); + + List log = performLog(); + + String[] expected = {"Shutting down", transport, "port ", String.valueOf(port)}; + + validateLogMessage(log, "BRK-1003", expected); + } + + public void testBrokerReady() + { + _logMessage = BrokerMessages.READY(); + List log = performLog(); + + String[] expected = {"Ready"}; + + validateLogMessage(log, "BRK-1004", expected); + } + + public void testBrokerStopped() + { + _logMessage = BrokerMessages.STOPPED(); + List log = performLog(); + + String[] expected = {"Stopped"}; + + validateLogMessage(log, "BRK-1005", expected); + } + + public void testBrokerConfig() + { + String path = "/file/path/to/configuration.xml"; + + _logMessage = BrokerMessages.CONFIG(path); + List log = performLog(); + + String[] expected = {"Using configuration :", path}; + + validateLogMessage(log, "BRK-1006", expected); + } + + public void testBrokerLogConfig() + { + String path = "/file/path/to/configuration.xml"; + + _logMessage = BrokerMessages.LOG_CONFIG(path); + List log = performLog(); + + String[] expected = {"Using logging configuration :", path}; + + validateLogMessage(log, "BRK-1007", expected); + } + + public void testBrokerPlatform() + { + String javaVendor = "jvendor"; + String javaVersion = "j1.0"; + + String osName = "os"; + String osVersion = "o1.0"; + String osArch = "oarch"; + + _logMessage = BrokerMessages.PLATFORM(javaVendor, javaVersion, osName, osVersion, osArch); + List log = performLog(); + + String[] expected = {"Platform :", "JVM :", javaVendor, " version: ", " OS : ", osName, " version: ", osVersion, " arch: ", osArch}; + + validateLogMessage(log, "BRK-1010", expected); + } + + public void testBrokerMemory() + { + long oneGiga = 1024*1024*1024; + + _logMessage = BrokerMessages.MAX_MEMORY(oneGiga); + List log = performLog(); + + String[] expected = {"Maximum Memory :", NumberFormat.getNumberInstance().format(oneGiga), "bytes"}; + + validateLogMessage(log, "BRK-1011", expected); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ChannelMessagesTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ChannelMessagesTest.java new file mode 100644 index 0000000000..2f1276508c --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ChannelMessagesTest.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.logging.messages; + +import java.util.List; + +/** + * Test CHN Log Messges + */ +public class ChannelMessagesTest extends AbstractTestMessages +{ + public void testChannelCreate() + { + _logMessage = ChannelMessages.CREATE(); + List log = performLog(); + + // We use the MessageFormat here as that is what the ChannelMessage + // will do, this makes the resulting value 12,345 + String[] expected = {"Create"}; + + validateLogMessage(log, "CHN-1001", expected); + } + + public void testChannelFlow() + { + String flow = "ON"; + + _logMessage = ChannelMessages.FLOW(flow); + List log = performLog(); + + String[] expected = {"Flow", flow}; + + validateLogMessage(log, "CHN-1002", expected); + } + + public void testChannelClose() + { + _logMessage = ChannelMessages.CLOSE(); + List log = performLog(); + + String[] expected = {"Close"}; + + validateLogMessage(log, "CHN-1003", expected); + } + + public void testChannelCloseForced() + { + _logMessage = ChannelMessages.CLOSE_FORCED(1, "Test"); + List log = performLog(); + + String[] expected = {"Close : 1 - Test"}; + + validateLogMessage(log, "CHN-1003", expected); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ConnectionMessagesTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ConnectionMessagesTest.java new file mode 100644 index 0000000000..b2951ae54a --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ConnectionMessagesTest.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.logging.messages; + +import java.util.List; + +/** + * Test CON Log Messages + */ +public class ConnectionMessagesTest extends AbstractTestMessages +{ + public void testConnectionOpen_WithClientIDProtocolVersionClientVersion() + { + String clientID = "client"; + String protocolVersion = "8-0"; + String clientVersion = "1.2.3_4"; + + _logMessage = ConnectionMessages.OPEN(clientID, protocolVersion, clientVersion, true , true, true); + List log = performLog(); + + String[] expected = {"Open :", "Client ID", clientID, + ": Protocol Version :", protocolVersion, + ": Client Version :", clientVersion}; + + validateLogMessage(log, "CON-1001", expected); + } + + public void testConnectionOpen_WithClientIDNoProtocolVersionNoClientVersion() + { + String clientID = "client"; + + _logMessage = ConnectionMessages.OPEN(clientID, null, null, true, false, false); + List log = performLog(); + + String[] expected = {"Open :", "Client ID", clientID}; + + validateLogMessage(log, "CON-1001", expected); + } + + public void testConnectionOpen_WithNOClientIDProtocolVersionNoClientVersion() + { + String protocolVersion = "8-0"; + + _logMessage = ConnectionMessages.OPEN(null, protocolVersion, null, false , true, false); + List log = performLog(); + + String[] expected = {"Open", ": Protocol Version :", protocolVersion}; + + validateLogMessage(log, "CON-1001", expected); + } + + public void testConnectionOpen_WithNOClientIDNoProtocolVersionClientVersion() + { + String clientVersion = "1.2.3_4"; + + _logMessage = ConnectionMessages.OPEN(null, null, clientVersion, false , false, true); + List log = performLog(); + + String[] expected = {"Open", ": Client Version :", clientVersion}; + + validateLogMessage(log, "CON-1001", expected); + } + + public void testConnectionOpen_WithNOClientIDNoProtocolVersionNullClientVersion() + { + String clientVersion = null; + + _logMessage = ConnectionMessages.OPEN(null, null, clientVersion , false , false, true); + List log = performLog(); + + String[] expected = {"Open", ": Client Version :", clientVersion}; + + validateLogMessage(log, "CON-1001", true, expected); + } + + public void testConnectionOpen_WithNoClientIDNoProtocolVersionNoClientVersion() + { + _logMessage = ConnectionMessages.OPEN(null, null, null, false, false, false); + List log = performLog(); + + String[] expected = {"Open"}; + + validateLogMessage(log, "CON-1001", expected); + } + + public void testConnectionClose() + { + _logMessage = ConnectionMessages.CLOSE(); + List log = performLog(); + + String[] expected = {"Close"}; + + validateLogMessage(log, "CON-1002", expected); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ExchangeMessagesTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ExchangeMessagesTest.java new file mode 100644 index 0000000000..f1452b8b88 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ExchangeMessagesTest.java @@ -0,0 +1,87 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.messages; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.util.BrokerTestHelper; + +import java.util.List; + +/** + * Test EXH Log Messages + */ +public class ExchangeMessagesTest extends AbstractTestMessages +{ + public void testExchangeCreated_Transient() throws Exception + { + Exchange exchange = BrokerTestHelper.createExchange("test"); + + String type = exchange.getTypeName(); + String name = exchange.getName(); + + _logMessage = ExchangeMessages.CREATED(type, name, false); + List log = performLog(); + + String[] expected = {"Create :", "Type:", type, "Name:", name}; + + validateLogMessage(log, "EXH-1001", expected); + } + + public void testExchangeCreated_Persistent() throws Exception + { + Exchange exchange = BrokerTestHelper.createExchange("test"); + + String type = exchange.getTypeName(); + String name = exchange.getName(); + + _logMessage = ExchangeMessages.CREATED(type, name, true); + List log = performLog(); + + String[] expected = {"Create :", "Durable", "Type:", type, "Name:", name}; + + validateLogMessage(log, "EXH-1001", expected); + } + + public void testExchangeDeleted() + { + _logMessage = ExchangeMessages.DELETED(); + List log = performLog(); + + String[] expected = {"Deleted"}; + + validateLogMessage(log, "EXH-1002", expected); + } + + public void testExchangeDiscardedMessage() throws Exception + { + Exchange exchange = BrokerTestHelper.createExchange("test"); + + final String name = exchange.getName(); + final String routingKey = "routingKey"; + + _logMessage = ExchangeMessages.DISCARDMSG(name, routingKey); + List log = performLog(); + + String[] expected = {"Discarded Message :","Name:", name, "Routing Key:", routingKey}; + + validateLogMessage(log, "EXH-1003", expected); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ManagementConsoleMessagesTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ManagementConsoleMessagesTest.java new file mode 100644 index 0000000000..dfc9357402 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/ManagementConsoleMessagesTest.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.logging.messages; + +import java.util.List; + +/** + * Test MNG Log Messages + */ +public class ManagementConsoleMessagesTest extends AbstractTestMessages +{ + public void testManagementStartup() + { + _logMessage = ManagementConsoleMessages.STARTUP("My"); + List log = performLog(); + + String[] expected = {"My Management Startup"}; + + validateLogMessage(log, "MNG-1001", expected); + } + + public void testManagementListening() + { + String transport = "JMX"; + Integer port = 8889; + + _logMessage = ManagementConsoleMessages.LISTENING(transport, port); + List log = performLog(); + + String[] expected = {"Starting :", transport, ": Listening on port", String.valueOf(port)}; + + validateLogMessage(log, "MNG-1002", expected); + } + + public void testManagementShuttingDown() + { + String transport = "JMX"; + Integer port = 8889; + + _logMessage = ManagementConsoleMessages.SHUTTING_DOWN(transport, port); + List log = performLog(); + + String[] expected = {"Shutting down :", transport, ": port", String.valueOf(port)}; + + validateLogMessage(log, "MNG-1003", expected); + } + + public void testManagementReady() + { + _logMessage = ManagementConsoleMessages.READY("My"); + List log = performLog(); + + String[] expected = {"My Management Ready"}; + + validateLogMessage(log, "MNG-1004", expected); + } + + public void testManagementStopped() + { + _logMessage = ManagementConsoleMessages.STOPPED("My"); + List log = performLog(); + + String[] expected = {"My Management Stopped"}; + + validateLogMessage(log, "MNG-1005", expected); + } + + public void testManagementSSLKeyStore() + { + String path = "/path/to/the/keystore/files.jks"; + + _logMessage = ManagementConsoleMessages.SSL_KEYSTORE(path); + List log = performLog(); + + String[] expected = {"Using SSL Keystore :", path}; + + validateLogMessage(log, "MNG-1006", expected); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/MessageStoreMessagesTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/MessageStoreMessagesTest.java new file mode 100644 index 0000000000..3377573b9d --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/MessageStoreMessagesTest.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.logging.messages; + +import java.util.List; + +/** + * Test MST Log Messages + */ +public class MessageStoreMessagesTest extends AbstractTestMessages +{ + public void testMessageStoreCreated() + { + _logMessage = MessageStoreMessages.CREATED(); + List log = performLog(); + + String[] expected = {"Created"}; + + validateLogMessage(log, "MST-1001", expected); + } + + public void testMessageStoreStoreLocation() + { + String location = "/path/to/the/message/store.files"; + + _logMessage = MessageStoreMessages.STORE_LOCATION(location); + List log = performLog(); + + String[] expected = {"Store location :", location}; + + validateLogMessage(log, "MST-1002", expected); + } + + public void testMessageStoreClosed() + { + _logMessage = MessageStoreMessages.CLOSED(); + List log = performLog(); + + String[] expected = {"Closed"}; + + validateLogMessage(log, "MST-1003", expected); + } + + public void testMessageStoreRecoveryStart() + { + _logMessage = MessageStoreMessages.RECOVERY_START(); + List log = performLog(); + + String[] expected = {"Recovery Start"}; + + validateLogMessage(log, "MST-1004", expected); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/QueueMessagesTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/QueueMessagesTest.java new file mode 100644 index 0000000000..d51e6a6bb7 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/QueueMessagesTest.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.logging.messages; + +import java.util.List; + +/** + * Test QUE Log Messages + */ +public class QueueMessagesTest extends AbstractTestMessages +{ + public void testQueueCreatedALL() + { + String owner = "guest"; + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(owner, priority, true, true, true, true, true); + List log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Durable", "Transient", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDelete() + { + String owner = "guest"; + + _logMessage = QueueMessages.CREATED(owner, null, true, true, false, false, false); + List log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerPriority() + { + String owner = "guest"; + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(owner, priority, true, false, false, false, true); + List log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDeletePriority() + { + String owner = "guest"; + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(owner, priority, true, true, false, false, true); + List log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDeleteTransient() + { + String owner = "guest"; + + _logMessage = QueueMessages.CREATED(owner, null, true, true, false, true, false); + List log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Transient"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDeleteTransientPriority() + { + String owner = "guest"; + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(owner, priority, true, true, false, true, true); + List log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Transient", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDeleteDurable() + { + String owner = "guest"; + + _logMessage = QueueMessages.CREATED(owner, null, true, true, true, false, false); + List log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Durable"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedOwnerAutoDeleteDurablePriority() + { + String owner = "guest"; + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(owner, priority, true, true, true, false, true); + List log = performLog(); + + String[] expected = {"Create :", "Owner:", owner, "AutoDelete", + "Durable", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDelete() + { + _logMessage = QueueMessages.CREATED(null, null, false, true, false, false, false); + List log = performLog(); + + String[] expected = {"Create :", "AutoDelete"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedPriority() + { + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(null, priority, false, false, false, false, true); + List log = performLog(); + + String[] expected = {"Create :", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDeletePriority() + { + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(null, priority, false, true, false, false, true); + List log = performLog(); + + String[] expected = {"Create :", "AutoDelete", + "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDeleteTransient() + { + _logMessage = QueueMessages.CREATED(null, null, false, true, false, true, false); + List log = performLog(); + + String[] expected = {"Create :", "AutoDelete", + "Transient"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDeleteTransientPriority() + { + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(null, priority, false, true, false, true, true); + List log = performLog(); + + String[] expected = {"Create :", "AutoDelete", + "Transient", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDeleteDurable() + { + _logMessage = QueueMessages.CREATED(null, null, false, true, true, false, false); + List log = performLog(); + + String[] expected = {"Create :", "AutoDelete", + "Durable"}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueCreatedAutoDeleteDurablePriority() + { + Integer priority = 3; + + _logMessage = QueueMessages.CREATED(null, priority, false, true, true, false, true); + List log = performLog(); + + String[] expected = {"Create :", "AutoDelete", + "Durable", "Priority:", + String.valueOf(priority)}; + + validateLogMessage(log, "QUE-1001", expected); + } + + public void testQueueDeleted() + { + _logMessage = QueueMessages.DELETED(); + List log = performLog(); + + String[] expected = {"Deleted"}; + + validateLogMessage(log, "QUE-1002", expected); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/SubscriptionMessagesTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/SubscriptionMessagesTest.java new file mode 100644 index 0000000000..b2bc351f8f --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/SubscriptionMessagesTest.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.logging.messages; + +import java.util.List; + +/** + * Test SUB Log Messages + */ +public class SubscriptionMessagesTest extends AbstractTestMessages +{ + public void testSubscriptionCreateALL() + { + String arguments = "arguments"; + + _logMessage = SubscriptionMessages.CREATE(arguments, true, true); + List log = performLog(); + + String[] expected = {"Create :", "Durable", "Arguments :", arguments}; + + validateLogMessage(log, "SUB-1001", expected); + } + + public void testSubscriptionCreateDurable() + { + _logMessage = SubscriptionMessages.CREATE(null, true, false); + List log = performLog(); + + String[] expected = {"Create :", "Durable"}; + + validateLogMessage(log, "SUB-1001", expected); + } + + public void testSubscriptionCreateArguments() + { + String arguments = "arguments"; + + _logMessage = SubscriptionMessages.CREATE(arguments, false, true); + List log = performLog(); + + String[] expected = {"Create :","Arguments :", arguments}; + + validateLogMessage(log, "SUB-1001", expected); + } + + + public void testSubscriptionClose() + { + _logMessage = SubscriptionMessages.CLOSE(); + List log = performLog(); + + String[] expected = {"Close"}; + + validateLogMessage(log, "SUB-1002", expected); + } + + public void testSubscriptionState() + { + String state = "ACTIVE"; + + _logMessage = SubscriptionMessages.STATE(state); + List log = performLog(); + + String[] expected = {"State :", state}; + + validateLogMessage(log, "SUB-1003", expected); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/VirtualHostMessagesTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/VirtualHostMessagesTest.java new file mode 100644 index 0000000000..17d68ef7c3 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/messages/VirtualHostMessagesTest.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.logging.messages; + +import java.util.List; + +/** + * Test VHT Log Messages + */ +public class VirtualHostMessagesTest extends AbstractTestMessages +{ + public void testVirtualhostCreated() + { + String name = "test"; + _logMessage = VirtualHostMessages.CREATED(name); + List log = performLog(); + + String[] expected = {"Created :", name}; + + validateLogMessage(log, "VHT-1001", expected); + } + + public void testSubscriptionClosed() + { + _logMessage = VirtualHostMessages.CLOSED(); + List log = performLog(); + + String[] expected = {"Closed"}; + + validateLogMessage(log, "VHT-1002", expected); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/AbstractTestLogSubject.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/AbstractTestLogSubject.java new file mode 100644 index 0000000000..cd8f13d52e --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/AbstractTestLogSubject.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. + * + */ +package org.apache.qpid.server.logging.subjects; + + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.UnitTestMessageLogger; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.TestLogActor; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +import java.util.List; + +/** + * Abstract Test for LogSubject testing + * Includes common validation code and two common tests. + * + * Each test class sets up the LogSubject and contains details of how to + * validate this class then performs a log statement with logging enabled and + * logging disabled. + * + * The resulting log file is then validated. + * + */ +public abstract class AbstractTestLogSubject extends QpidTestCase +{ + protected LogSubject _subject = null; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + } + + @Override + public void tearDown() throws Exception + { + BrokerTestHelper.tearDown(); + try + { + CurrentActor.removeAll(); + } + finally + { + super.tearDown(); + } + } + + protected List performLog(boolean statusUpdatesEnabled) + { + if (_subject == null) + { + throw new NullPointerException("LogSubject has not been set"); + } + + UnitTestMessageLogger logger = new UnitTestMessageLogger(statusUpdatesEnabled); + + LogActor actor = new TestLogActor(logger); + + actor.message(_subject, new LogMessage() + { + public String toString() + { + return ""; + } + + public String getLogHierarchy() + { + return "test.hierarchy"; + } + }); + + return logger.getLogMessages(); + } + + /** + * Verify that the connection section has the expected items + * + * @param connectionID - The connection id (int) to check for + * @param user - the Connected username + * @param ipString - the ipString/hostname + * @param vhost - the virtualhost that the user connected to. + * @param message - the message these values should appear in. + */ + protected void verifyConnection(long connectionID, String user, String ipString, String vhost, String message) + { + // This should return us MockProtocolSessionUser@null/test + String connectionSlice = getSlice("con:" + connectionID, message); + + assertNotNull("Unable to find connection 'con:" + connectionID + "' in '"+message+"'", + connectionSlice); + + // Exract the userName + String[] userNameParts = connectionSlice.split("@"); + + assertEquals("Unable to split Username from rest of Connection:" + + connectionSlice, 2, userNameParts.length); + + assertEquals("Username not as expected", userNameParts[0], user); + + // Extract IP. + // The connection will be of the format - guest@/127.0.0.1:1/test + // and so our userNamePart will be '/127.0.0.1:1/test' + String[] ipParts = userNameParts[1].split("/"); + + // We will have three sections + assertEquals("Unable to split IP from rest of Connection:" + + userNameParts[1] + " in '"+message+"'", 3, ipParts.length); + + // We need to skip the first '/' split will be empty so validate 1 as IP + assertEquals("IP not as expected", ipString, ipParts[1]); + + //Finally check vhost which is section 2 + assertEquals("Virtualhost name not as expected.", vhost, ipParts[2]); + } + + /** + * Verify that the RoutingKey is present in the provided message. + * + * @param message The message to check + * @param routingKey The routing key to check against + */ + protected void verifyRoutingKey(String message, String routingKey) + { + String routingKeySlice = getSlice("rk", message); + + assertNotNull("Routing Key not found:" + message, routingKeySlice); + + assertEquals("Routing key not correct", + routingKey, routingKeySlice); + } + + /** + * Verify that the given Queue's name exists in the provided message + * + * @param message The message to check + * @param queue The queue to check against + */ + protected void verifyQueue(String message, AMQQueue queue) + { + String queueSlice = getSlice("qu", message); + + assertNotNull("Queue not found:" + message, queueSlice); + + assertEquals("Queue name not correct", + queue.getName(), queueSlice); + } + + /** + * Verify that the given exchange (name and type) are present in the + * provided message. + * + * @param message The message to check + * @param exchange the exchange to check against + */ + protected void verifyExchange(String message, Exchange exchange) + { + String exchangeSilce = getSlice("ex", message); + + assertNotNull("Exchange not found:" + message, exchangeSilce); + + String[] exchangeParts = exchangeSilce.split("/"); + + assertEquals("Exchange should be in two parts ex(type/name)", 2, + exchangeParts.length); + + assertEquals("Exchange type not correct", + exchange.getType().getType(), exchangeParts[0]); + + assertEquals("Exchange name not correct", + exchange.getName(), exchangeParts[1]); + + } + + /** + * Verify that a VirtualHost with the given name appears in the given + * message. + * + * @param message the message to search + * @param vhost the vhostName to check against + */ + static public void verifyVirtualHost(String message, VirtualHost vhost) + { + String vhostSlice = getSlice("vh", message); + + assertNotNull("Virtualhost not found:" + message, vhostSlice); + + assertEquals("Virtualhost not correct", "/" + vhost.getName(), vhostSlice); + } + + /** + * Parse the log message and return the slice according to the following: + * Given Example: + * con:1(guest@127.0.0.1/test)/ch:2/ex(amq.direct)/qu(myQueue)/rk(myQueue) + * + * Each item (except channel) is of the format () + * + * So Given an ID to slice on: + * con:1 - Connection 1 + * ex - exchange + * qu - queue + * rk - routing key + * + * @param sliceID the slice to locate + * @param message the message to search in + * + * @return the slice if found otherwise null is returned + */ + static public String getSlice(String sliceID, String message) + { + int indexOfSlice = message.indexOf(sliceID + "("); + + if (indexOfSlice == -1) + { + return null; + } + + int endIndex = message.indexOf(')', indexOfSlice); + + if (endIndex == -1) + { + return null; + } + + return message.substring(indexOfSlice + 1 + sliceID.length(), + endIndex); + } + + /** + * Test that when Logging occurs a single log statement is provided + * + */ + public void testEnabled() + { + List logs = performLog(true); + + assertEquals("Log has incorrect message count", 1, logs.size()); + + validateLogStatement(String.valueOf(logs.get(0))); + } + + /** + * Call to the individiual tests to validate the message is formatted as + * expected + * + * @param message the message whos format needs validation + */ + protected abstract void validateLogStatement(String message); + + /** + * Ensure that when status updates are off this does not perform logging + */ + public void testDisabled() + { + List logs = performLog(false); + + assertEquals("Log has incorrect message count", 0, logs.size()); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/BindingLogSubjectTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/BindingLogSubjectTest.java new file mode 100644 index 0000000000..e52ead451e --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/BindingLogSubjectTest.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.logging.subjects; + +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.queue.MockAMQQueue; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * Validate BindingLogSubjects are logged as expected + */ +public class BindingLogSubjectTest extends AbstractTestLogSubject +{ + + private AMQQueue _queue; + private String _routingKey; + private Exchange _exchange; + private VirtualHost _testVhost; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _testVhost = BrokerTestHelper.createVirtualHost("test"); + _routingKey = "RoutingKey"; + _exchange = _testVhost.getExchange("amq.direct"); + _queue = new MockAMQQueue("BindingLogSubjectTest"); + ((MockAMQQueue) _queue).setVirtualHost(_testVhost); + + _subject = new BindingLogSubject(_routingKey, _exchange, _queue); + } + + @Override + public void tearDown() throws Exception + { + if (_testVhost != null) + { + _testVhost.close(); + } + super.tearDown(); + } + + /** + * Validate that the logged Subject message is as expected: + * MESSAGE [Blank][vh(/test)/ex(direct/<>)/qu(BindingLogSubjectTest)/rk(RoutingKey)] + * @param message the message whos format needs validation + */ + @Override + protected void validateLogStatement(String message) + { + verifyVirtualHost(message, _testVhost); + verifyExchange(message, _exchange); + verifyQueue(message, _queue); + verifyRoutingKey(message, _routingKey); + } + + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ChannelLogSubjectTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ChannelLogSubjectTest.java new file mode 100644 index 0000000000..a3d96c6d12 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ChannelLogSubjectTest.java @@ -0,0 +1,62 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.subjects; + +import org.apache.qpid.server.protocol.AMQSessionModel; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Validate ChannelLogSubjects are logged as expected + */ +public class ChannelLogSubjectTest extends ConnectionLogSubjectTest +{ + private final int _channelID = 1; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + AMQSessionModel session = mock(AMQSessionModel.class); + when(session.getConnectionModel()).thenReturn(getConnection()); + when(session.getChannelId()).thenReturn(_channelID); + _subject = new ChannelLogSubject(session); + } + + /** + * MESSAGE [Blank][con:0(MockProtocolSessionUser@null/test)/ch:1] + * + * @param message the message whos format needs validation + */ + protected void validateLogStatement(String message) + { + // Use the ConnectionLogSubjectTest to vaildate that the connection + // section is ok + super.validateLogStatement(message); + + // Finally check that the channel identifier is correctly added + assertTrue("Channel 1 identifier not found as part of Subject", + message.contains(")/ch:1]")); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubjectTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubjectTest.java new file mode 100644 index 0000000000..e9a9317102 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubjectTest.java @@ -0,0 +1,69 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.logging.subjects; + +import org.apache.qpid.server.protocol.AMQConnectionModel; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Validate ConnectionLogSubjects are logged as expected + */ +public class ConnectionLogSubjectTest extends AbstractTestLogSubject +{ + + private static final long CONNECTION_ID = 456l; + private static final String USER = "InternalTestProtocolSession"; + private static final String IP_STRING = "127.0.0.1:1"; + private static final String VHOST = "test"; + + private AMQConnectionModel _connection; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _connection = mock(AMQConnectionModel.class); + when(_connection.getConnectionId()).thenReturn(CONNECTION_ID); + when(_connection.getPrincipalAsString()).thenReturn(USER); + when(_connection.getRemoteAddressString()).thenReturn("/"+IP_STRING); + when(_connection.getVirtualHostName()).thenReturn(VHOST); + _subject = new ConnectionLogSubject(_connection); + } + + /** + * MESSAGE [Blank][con:0(MockProtocolSessionUser@null/test)] + * + * @param message the message whos format needs validation + */ + protected void validateLogStatement(String message) + { + verifyConnection(CONNECTION_ID, USER, IP_STRING, VHOST, message); + } + + public AMQConnectionModel getConnection() + { + return _connection; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubjectTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubjectTest.java new file mode 100644 index 0000000000..b327738797 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubjectTest.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.logging.subjects; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; + + +/** + * Validate ExchangeLogSubjects are logged as expected + */ +public class ExchangeLogSubjectTest extends AbstractTestLogSubject +{ + private Exchange _exchange; + private VirtualHost _testVhost; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _testVhost = BrokerTestHelper.createVirtualHost("test"); + + _exchange = _testVhost.getExchange("amq.direct"); + _subject = new ExchangeLogSubject(_exchange,_testVhost); + } + + @Override + public void tearDown() throws Exception + { + if (_testVhost != null) + { + _testVhost.close(); + } + super.tearDown(); + } + + /** + * Validate that the logged Subject message is as expected: + * MESSAGE [Blank][vh(/test)/ex(direct/<>)] + * @param message the message whos format needs validation + */ + @Override + protected void validateLogStatement(String message) + { + verifyVirtualHost(message, _testVhost); + verifyExchange(message, _exchange); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubjectTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubjectTest.java new file mode 100644 index 0000000000..3d43ef0f44 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubjectTest.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.logging.subjects; + +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * Validate MessageStoreLogSubjects are logged as expected + */ +public class MessageStoreLogSubjectTest extends AbstractTestLogSubject +{ + private VirtualHost _testVhost; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _testVhost = BrokerTestHelper.createVirtualHost("test"); + + _subject = new MessageStoreLogSubject(_testVhost.getName(), + _testVhost.getMessageStore().getClass().getSimpleName()); + } + + @Override + public void tearDown() throws Exception + { + if (_testVhost != null) + { + _testVhost.close(); + } + super.tearDown(); + } + + /** + * Validate that the logged Subject message is as expected: + * MESSAGE [Blank][vh(/test)/ms(MemoryMessageStore)] + * @param message the message who's format needs validation + */ + @Override + protected void validateLogStatement(String message) + { + verifyVirtualHost(message, _testVhost); + + String msSlice = getSlice("ms", message); + + assertNotNull("MessageStore not found:" + message, msSlice); + + assertEquals("MessageStore not correct", + _testVhost.getMessageStore().getClass().getSimpleName(), + msSlice); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/QueueLogSubjectTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/QueueLogSubjectTest.java new file mode 100644 index 0000000000..e2765f338b --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/QueueLogSubjectTest.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.logging.subjects; + +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * Validate QueueLogSubjects are logged as expected + */ +public class QueueLogSubjectTest extends AbstractTestLogSubject +{ + + private AMQQueue _queue; + private VirtualHost _testVhost; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _testVhost = BrokerTestHelper.createVirtualHost("test"); + + _queue = new MockAMQQueue("QueueLogSubjectTest"); + ((MockAMQQueue) _queue).setVirtualHost(_testVhost); + + _subject = new QueueLogSubject(_queue); + } + + @Override + public void tearDown() throws Exception + { + if (_testVhost != null) + { + _testVhost.close(); + } + super.tearDown(); + } + + /** + * Validate that the logged Subject message is as expected: + * MESSAGE [Blank][vh(/test)/qu(QueueLogSubjectTest)] + * + * @param message the message whos format needs validation + */ + @Override + protected void validateLogStatement(String message) + { + verifyVirtualHost(message, _testVhost); + verifyQueue(message, _queue); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubjectTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubjectTest.java new file mode 100644 index 0000000000..b9efac1ae8 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubjectTest.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.logging.subjects; + +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Validate SubscriptionLogSubjects are logged as expected + */ +public class SubscriptionLogSubjectTest extends AbstractTestLogSubject +{ + + private static final long SUBSCRIPTION_ID = 1; + private AMQQueue _queue; + private VirtualHost _testVhost; + private Subscription _subscription; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _testVhost = BrokerTestHelper.createVirtualHost("test"); + + _queue = new MockAMQQueue("SubscriptionLogSubjectTest"); + ((MockAMQQueue) _queue).setVirtualHost(_testVhost); + + _subscription = mock(Subscription.class); + when(_subscription.getQueue()).thenReturn(_queue); + when(_subscription.getSubscriptionID()).thenReturn(SUBSCRIPTION_ID); + + _subject = new SubscriptionLogSubject(_subscription); + } + + @Override + public void tearDown() throws Exception + { + if (_testVhost != null) + { + _testVhost.close(); + } + super.tearDown(); + } + + /** + * Validate that the logged Subject message is as expected: + * MESSAGE [Blank][sub:0(vh(/test)/qu(SubscriptionLogSubjectTest))] + * + * @param message the message whos format needs validation + */ + @Override + protected void validateLogStatement(String message) + { + String subscriptionSlice = getSlice("sub:" + + _subscription.getSubscriptionID(), + message); + + assertNotNull("Unable to locate subscription 'sub:" + + _subscription.getSubscriptionID() + "'"); + + + + // Pull out the qu(..) section from the subscription message + // Split it into three parts + // MESSAGE [Blank][sub:0(vh(/ + // test)/ + // qu(SubscriptionLogSubjectTest))] + // Take the last bit and drop off the extra )] + String[] parts = message.split("/"); + assertEquals("Message part count wrong", 3, parts.length); + String subscription = parts[2].substring(0, parts[2].indexOf(")") + 1); + + // Adding the ')' is a bit ugly but SubscriptionLogSubject is the only + // Subject that nests () and so the simple parser of checking for the + // next ')' falls down. + verifyVirtualHost(subscriptionSlice+ ")", _queue.getVirtualHost()); + + verifyQueue(subscription, _queue); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/TestBlankSubject.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/TestBlankSubject.java new file mode 100644 index 0000000000..7684db0b3d --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/logging/subjects/TestBlankSubject.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.logging.subjects; + +/** + * Blank Subject for testing + */ +public class TestBlankSubject extends AbstractLogSubject +{ + public TestBlankSubject() + { + setLogString("[TestBlankSubject]"); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/ConfiguredObjectStateTransitionTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/ConfiguredObjectStateTransitionTest.java new file mode 100644 index 0000000000..72cf09585c --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/ConfiguredObjectStateTransitionTest.java @@ -0,0 +1,266 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.BrokerOptions; +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.configuration.ConfiguredObjectRecoverer; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.configuration.startup.DefaultRecovererProvider; +import org.apache.qpid.server.configuration.store.StoreConfigurationChangeListener; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.security.auth.manager.AnonymousAuthenticationManagerFactory; +import org.apache.qpid.server.security.group.FileGroupManagerFactory; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.test.utils.QpidTestCase; + +public class ConfiguredObjectStateTransitionTest extends QpidTestCase +{ + private Broker _broker; + private RecovererProvider _recovererProvider; + private ConfigurationEntryStore _store; + private File _resourceToDelete; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + + _broker = BrokerTestHelper.createBrokerMock(); + StatisticsGatherer statisticsGatherer = mock(StatisticsGatherer.class); + TaskExecutor executor = mock(TaskExecutor.class); + when(executor.isTaskExecutorThread()).thenReturn(true); + when(_broker.getTaskExecutor()).thenReturn(executor); + + _recovererProvider = new DefaultRecovererProvider(statisticsGatherer, _broker.getVirtualHostRegistry(), + _broker.getLogRecorder(), _broker.getRootMessageLogger(), executor, new BrokerOptions(), mock(StoreConfigurationChangeListener.class)); + + _store = mock(ConfigurationEntryStore.class); + + _resourceToDelete = new File(TMP_FOLDER, getTestName()); + } + + @Override + public void tearDown() throws Exception + { + try + { + BrokerTestHelper.tearDown(); + if (_resourceToDelete.exists()) + { + _resourceToDelete.delete(); + } + } + finally + { + super.tearDown(); + } + } + + public void testGroupProviderValidStateTransitions() throws Exception + { + ConfigurationEntry providerEntry = getGroupProviderConfigurationEntry(); + ConfiguredObject provider = createConfiguredObject(providerEntry); + provider.setDesiredState(State.INITIALISING, State.QUIESCED); + assertValidStateTransition(provider, State.QUIESCED, State.STOPPED); + + provider = createConfiguredObject(providerEntry); + assertValidStateTransition(provider, State.INITIALISING, State.DELETED); + + providerEntry = getGroupProviderConfigurationEntry(); + provider = createConfiguredObject(providerEntry); + provider.setDesiredState(State.INITIALISING, State.QUIESCED); + assertValidStateTransition(provider, State.QUIESCED, State.DELETED); + + providerEntry = getGroupProviderConfigurationEntry(); + provider = createConfiguredObject(providerEntry); + provider.setDesiredState(State.INITIALISING, State.ACTIVE); + assertValidStateTransition(provider, State.ACTIVE, State.DELETED); + } + + public void testGroupProviderInvalidStateTransitions() throws Exception + { + ConfigurationEntry providerEntry = getGroupProviderConfigurationEntry(); + assertAllInvalidStateTransitions(providerEntry); + } + + public void testAuthenticationProviderValidStateTransitions() + { + ConfigurationEntry providerEntry = getAuthenticationProviderConfigurationEntry(); + assertAllValidStateTransitions(providerEntry); + } + + public void testAuthenticationProviderInvalidStateTransitions() + { + ConfigurationEntry providerEntry = getAuthenticationProviderConfigurationEntry(); + assertAllInvalidStateTransitions(providerEntry); + } + + public void testPortValidStateTransitions() + { + ConfigurationEntry providerEntry = getPortConfigurationEntry(); + assertAllValidStateTransitions(providerEntry); + } + + public void testPortInvalidStateTransitions() + { + ConfigurationEntry providerEntry = getPortConfigurationEntry(); + assertAllInvalidStateTransitions(providerEntry); + } + + private void assertAllInvalidStateTransitions(ConfigurationEntry providerEntry) + { + ConfiguredObject provider = createConfiguredObject(providerEntry); + assertInvalidStateTransition(provider, State.INITIALISING, State.REPLICA); + + provider.setDesiredState(State.INITIALISING, State.QUIESCED); + assertInvalidStateTransition(provider, State.QUIESCED, State.INITIALISING); + + provider.setDesiredState(State.QUIESCED, State.ACTIVE); + assertInvalidStateTransition(provider, State.ACTIVE, State.INITIALISING); + + provider.setDesiredState(State.ACTIVE, State.DELETED); + assertInvalidStateTransition(provider, State.DELETED, State.INITIALISING); + assertInvalidStateTransition(provider, State.DELETED, State.QUIESCED); + assertInvalidStateTransition(provider, State.DELETED, State.ACTIVE); + assertInvalidStateTransition(provider, State.DELETED, State.REPLICA); + assertInvalidStateTransition(provider, State.DELETED, State.ERRORED); + } + + private void assertAllValidStateTransitions(ConfigurationEntry providerEntry) + { + ConfiguredObject provider = createConfiguredObject(providerEntry); + assertNormalStateTransition(provider); + + provider = createConfiguredObject(providerEntry); + provider.setDesiredState(State.INITIALISING, State.QUIESCED); + assertValidStateTransition(provider, State.QUIESCED, State.STOPPED); + + provider = createConfiguredObject(providerEntry); + assertValidStateTransition(provider, State.INITIALISING, State.DELETED); + + provider = createConfiguredObject(providerEntry); + provider.setDesiredState(State.INITIALISING, State.QUIESCED); + assertValidStateTransition(provider, State.QUIESCED, State.DELETED); + + provider = createConfiguredObject(providerEntry); + provider.setDesiredState(State.INITIALISING, State.ACTIVE); + assertValidStateTransition(provider, State.ACTIVE, State.DELETED); + } + + private void assertInvalidStateTransition(ConfiguredObject object, State initialState, State... invalidStates) + { + assertEquals("Unepxceted state", initialState, object.getActualState()); + for (State state : invalidStates) + { + try + { + object.setDesiredState(initialState, state); + } + catch (IllegalStateException e) + { + // expected + } + assertEquals("Transition from state " + initialState + " into state " + state + " did occur", initialState, + object.getActualState()); + } + } + + private void assertValidStateTransition(ConfiguredObject object, State initialState, State... validStateSequence) + { + assertEquals("Unexpected state", initialState, object.getActualState()); + State currentState = initialState; + for (State state : validStateSequence) + { + object.setDesiredState(currentState, state); + assertEquals("Transition from state " + currentState + " into state " + state + " did not occur", state, + object.getActualState()); + currentState = state; + } + } + + private void assertNormalStateTransition(ConfiguredObject object) + { + assertValidStateTransition(object, State.INITIALISING, State.QUIESCED, State.ACTIVE, State.STOPPED, State.DELETED); + } + + private ConfiguredObject createConfiguredObject(ConfigurationEntry entry) + { + @SuppressWarnings("unchecked") + ConfiguredObjectRecoverer recoverer = + (ConfiguredObjectRecoverer)_recovererProvider.getRecoverer(entry.getType()); + return recoverer.create(_recovererProvider, entry, _broker); + } + + private ConfigurationEntry createConfigurationEntry(String type, Map attributes, ConfigurationEntryStore store) + { + return new ConfigurationEntry(UUID.randomUUID(), type, attributes, Collections.emptySet(), store); + } + + private ConfigurationEntry getAuthenticationProviderConfigurationEntry() + { + Map attributes = new HashMap(); + attributes.put(AuthenticationProvider.NAME, getTestName()); + attributes.put(AuthenticationProvider.TYPE, AnonymousAuthenticationManagerFactory.PROVIDER_TYPE); + return createConfigurationEntry(AuthenticationProvider.class.getSimpleName(), attributes , _store); + } + + private ConfigurationEntry getGroupProviderConfigurationEntry() throws Exception + { + Map attributes = new HashMap(); + attributes.put(GroupProvider.NAME, getTestName()); + attributes.put(GroupProvider.TYPE, FileGroupManagerFactory.GROUP_FILE_PROVIDER_TYPE); + attributes.put(FileGroupManagerFactory.PATH, _resourceToDelete.getAbsolutePath()); + if (!_resourceToDelete.exists()) + { + _resourceToDelete.createNewFile(); + } + return createConfigurationEntry(GroupProvider.class.getSimpleName(), attributes , _store); + } + + private ConfigurationEntry getPortConfigurationEntry() + { + ConfigurationEntry authProviderEntry = getAuthenticationProviderConfigurationEntry(); + AuthenticationProvider authProvider = (AuthenticationProvider)createConfiguredObject(authProviderEntry); + + Map attributes = new HashMap(); + attributes.put(Port.NAME, getTestName()); + attributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.HTTP)); + attributes.put(Port.AUTHENTICATION_PROVIDER, authProvider.getName()); + attributes.put(Port.PORT, 0); + + when(_broker.findAuthenticationProviderByName(authProvider.getName())).thenReturn(authProvider); + return createConfigurationEntry(Port.class.getSimpleName(), attributes , _store); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/UUIDGeneratorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/UUIDGeneratorTest.java new file mode 100644 index 0000000000..c686a24e99 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/UUIDGeneratorTest.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.model; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.apache.qpid.test.utils.QpidTestCase; + +public class UUIDGeneratorTest extends QpidTestCase +{ + private static final String VIRTUAL_HOST_NAME_1 = "virtualHost1"; + private static final String VIRTUAL_HOST_NAME_2 = "virtualHost2"; + private static final String VHOST_ALIAS_1 = "alias1"; + private static final String VHOST_ALIAS_2 = "alias2"; + private static final String QUEUE_NAME_1 = "queue1"; + private static final String QUEUE_NAME_2 = "queue2"; + private static final String EXCHANGE_NAME_1 = "exchange1"; + private static final String EXCHANGE_NAME_2 = "exchange2"; + private static final String BINDING_KEY_1 = "bindingKey1"; + private static final String BINDING_KEY_2 = "bindingKey2"; + private static final String PORT_1 = "port1"; + private static final String PORT_2 = "port2"; + private static final String CONN_REMOTE_ADDR_1 = "localhost:1234"; + private static final String CONN_REMOTE_ADDR_2 = "localhost:5678"; + private static final String CHANNEL_NUMBER_1 = "1"; + private static final String CHANNEL_NUMBER_2 = "2"; + private static final String CONSUMER_NAME_1 = "consumer1"; + private static final String CONSUMER_NAME_2 = "consumer2"; + private static final String PROVIDER_1 = "provider1"; + private static final String PROVIDER_2 = "provider2"; + private static final String USER_1 = "user1"; + private static final String USER_2 = "user2"; + + public void testDifferentObjectTypeReturnDifferentIdFromSameValues() throws Exception + { + String value = "name"; + Set idSet = new HashSet(); + + UUID id1 = UUIDGenerator.generateQueueUUID(value, value); + idSet.add(id1); + UUID id2 = UUIDGenerator.generateExchangeUUID(value, value); + idSet.add(id2); + UUID id3 = UUIDGenerator.generateBindingUUID(value, value, value, value); + idSet.add(id3); + UUID id4 = UUIDGenerator.generateConsumerUUID(value, value, value, value, value); + idSet.add(id4); + UUID id5 = UUIDGenerator.generateUserUUID(value, value); + idSet.add(id5); + UUID id6 = UUIDGenerator.generateVhostUUID(value); + idSet.add(id6); + UUID id7 = UUIDGenerator.generateVhostAliasUUID(value, value); + idSet.add(id7); + UUID id8 = UUIDGenerator.generateGroupUUID(value, value); + idSet.add(id8); + UUID id9 = UUIDGenerator.generateGroupMemberUUID(value, value, value); + idSet.add(id9); + + assertEquals("The produced UUIDs were not all unique", 9, idSet.size()); + } + + public void testQueueIdGeneration() throws Exception + { + //check repeated generation is deterministic + UUID queue1 = UUIDGenerator.generateQueueUUID(QUEUE_NAME_1, VIRTUAL_HOST_NAME_1); + UUID queue2 = UUIDGenerator.generateQueueUUID(QUEUE_NAME_1, VIRTUAL_HOST_NAME_1); + assertEquals("Queue IDs should be equal", queue1, queue2); + + //check different name gives different ID + queue1 = UUIDGenerator.generateQueueUUID(QUEUE_NAME_1, VIRTUAL_HOST_NAME_1); + queue2 = UUIDGenerator.generateQueueUUID(QUEUE_NAME_2, VIRTUAL_HOST_NAME_1); + assertFalse("Queue IDs should not be equal", queue1.equals(queue2)); + + //check different vhost name gives different ID + queue1 = UUIDGenerator.generateQueueUUID(QUEUE_NAME_1, VIRTUAL_HOST_NAME_1); + queue2 = UUIDGenerator.generateQueueUUID(QUEUE_NAME_1, VIRTUAL_HOST_NAME_2); + assertFalse("Queue IDs should not be equal", queue1.equals(queue2)); + } + + public void testExchangeIdGeneration() throws Exception + { + //check repeated generation is deterministic + UUID exchange1 = UUIDGenerator.generateExchangeUUID(EXCHANGE_NAME_1, VIRTUAL_HOST_NAME_1); + UUID exchange2 = UUIDGenerator.generateExchangeUUID(EXCHANGE_NAME_1, VIRTUAL_HOST_NAME_1); + assertEquals("Exchange IDs should be equal", exchange1, exchange2); + + //check different name gives different ID + exchange1 = UUIDGenerator.generateExchangeUUID(EXCHANGE_NAME_1, VIRTUAL_HOST_NAME_1); + exchange2 = UUIDGenerator.generateExchangeUUID(EXCHANGE_NAME_2, VIRTUAL_HOST_NAME_1); + assertFalse("Exchange IDs should not be equal", exchange1.equals(exchange2)); + + //check different vhost name gives different ID + exchange1 = UUIDGenerator.generateExchangeUUID(EXCHANGE_NAME_1, VIRTUAL_HOST_NAME_1); + exchange2 = UUIDGenerator.generateExchangeUUID(EXCHANGE_NAME_1, VIRTUAL_HOST_NAME_2); + assertFalse("Exchange IDs should not be equal", exchange1.equals(exchange2)); + } + + public void testBindingIdGeneration() throws Exception + { + //check repeated generation is deterministic + UUID binding1 = UUIDGenerator.generateBindingUUID(EXCHANGE_NAME_1, QUEUE_NAME_1, BINDING_KEY_1, VIRTUAL_HOST_NAME_1); + UUID binding2 = UUIDGenerator.generateBindingUUID(EXCHANGE_NAME_1, QUEUE_NAME_1, BINDING_KEY_1, VIRTUAL_HOST_NAME_1); + assertEquals("Binding IDs should be equal", binding1, binding2); + + //check different name gives different ID + binding1 = UUIDGenerator.generateBindingUUID(EXCHANGE_NAME_1, QUEUE_NAME_1, BINDING_KEY_1, VIRTUAL_HOST_NAME_1); + binding2 = UUIDGenerator.generateBindingUUID(EXCHANGE_NAME_1, QUEUE_NAME_1, BINDING_KEY_2, VIRTUAL_HOST_NAME_1); + assertFalse("Binding IDs should not be equal", binding1.equals(binding2)); + + //check different vhost name gives different ID + binding1 = UUIDGenerator.generateBindingUUID(EXCHANGE_NAME_1, QUEUE_NAME_1, BINDING_KEY_1, VIRTUAL_HOST_NAME_1); + binding2 = UUIDGenerator.generateBindingUUID(EXCHANGE_NAME_1, QUEUE_NAME_1, BINDING_KEY_1, VIRTUAL_HOST_NAME_2); + assertFalse("Binding IDs should not be equal", binding1.equals(binding2)); + } + + public void testVhostIdGeneration() throws Exception + { + //check repeated generation is deterministic + UUID vhost1 = UUIDGenerator.generateVhostUUID(VIRTUAL_HOST_NAME_1); + UUID vhost2 = UUIDGenerator.generateVhostUUID(VIRTUAL_HOST_NAME_1); + assertTrue("Virtualhost IDs should be equal", vhost1.equals(vhost2)); + + //check different vhost name gives different ID + vhost1 = UUIDGenerator.generateVhostUUID(VIRTUAL_HOST_NAME_1); + vhost2 = UUIDGenerator.generateVhostUUID(VIRTUAL_HOST_NAME_2); + assertFalse("Virtualhost IDs should not be equal", vhost1.equals(vhost2)); + } + + public void testVhostAliasIdGeneration() throws Exception + { + //check repeated generation is deterministic + UUID alias1 = UUIDGenerator.generateVhostAliasUUID(VHOST_ALIAS_1, PORT_1); + UUID alias2 = UUIDGenerator.generateVhostAliasUUID(VHOST_ALIAS_1, PORT_1); + assertTrue("Virtualhost Alias IDs should be equal", alias1.equals(alias2)); + + //check different port name gives different ID + alias1 = UUIDGenerator.generateVhostAliasUUID(VHOST_ALIAS_1, PORT_1); + alias2 = UUIDGenerator.generateVhostAliasUUID(VHOST_ALIAS_2, PORT_1); + assertFalse("Virtualhost Alias IDs should not be equal", alias1.equals(alias2)); + + //check different alias name gives different ID + alias1 = UUIDGenerator.generateVhostAliasUUID(VHOST_ALIAS_1, PORT_1); + alias2 = UUIDGenerator.generateVhostAliasUUID(VHOST_ALIAS_1, PORT_2); + assertFalse("Virtualhost Alias IDs should not be equal", alias1.equals(alias2)); + } + + public void testConsumerIdGeneration() throws Exception + { + //check repeated generation is deterministic + UUID consumer1 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_1, QUEUE_NAME_1, CONN_REMOTE_ADDR_1, CHANNEL_NUMBER_1, CONSUMER_NAME_1); + UUID consumer2 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_1, QUEUE_NAME_1, CONN_REMOTE_ADDR_1, CHANNEL_NUMBER_1, CONSUMER_NAME_1); + assertTrue("Consumer IDs should be equal", consumer1.equals(consumer2)); + + //check different name gives different ID + consumer1 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_1, QUEUE_NAME_1, CONN_REMOTE_ADDR_1, CHANNEL_NUMBER_1, CONSUMER_NAME_1); + consumer2 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_1, QUEUE_NAME_1, CONN_REMOTE_ADDR_1, CHANNEL_NUMBER_1, CONSUMER_NAME_2); + assertFalse("Consumer IDs should not be equal", consumer1.equals(consumer2)); + + //check different vhost name gives different ID + consumer1 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_1, QUEUE_NAME_1, CONN_REMOTE_ADDR_1, CHANNEL_NUMBER_1, CONSUMER_NAME_1); + consumer2 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_2, QUEUE_NAME_1, CONN_REMOTE_ADDR_1, CHANNEL_NUMBER_1, CONSUMER_NAME_1); + assertFalse("Consumer IDs should not be equal", consumer1.equals(consumer2)); + + //check different consumer name gives different ID + consumer1 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_1, QUEUE_NAME_1, CONN_REMOTE_ADDR_1, CHANNEL_NUMBER_1, CONSUMER_NAME_1); + consumer2 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_1, QUEUE_NAME_1, CONN_REMOTE_ADDR_1, CHANNEL_NUMBER_2, CONSUMER_NAME_1); + assertFalse("Consumer IDs should not be equal", consumer1.equals(consumer2)); + + //check different address name gives different ID + consumer1 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_1, QUEUE_NAME_1, CONN_REMOTE_ADDR_1, CHANNEL_NUMBER_1, CONSUMER_NAME_1); + consumer2 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_1, QUEUE_NAME_1, CONN_REMOTE_ADDR_2, CHANNEL_NUMBER_1, CONSUMER_NAME_1); + assertFalse("Consumer IDs should not be equal", consumer1.equals(consumer2)); + + //check different queue name gives different ID + consumer1 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_1, QUEUE_NAME_1, CONN_REMOTE_ADDR_1, CHANNEL_NUMBER_1, CONSUMER_NAME_1); + consumer2 = UUIDGenerator.generateConsumerUUID(VIRTUAL_HOST_NAME_1, QUEUE_NAME_2, CONN_REMOTE_ADDR_1, CHANNEL_NUMBER_1, CONSUMER_NAME_1); + assertFalse("Consumer IDs should not be equal", consumer1.equals(consumer2)); + } + + public void testUserIdGeneration() throws Exception + { + //check repeated generation is deterministic + UUID user1 = UUIDGenerator.generateUserUUID(PROVIDER_1, USER_1); + UUID user2 = UUIDGenerator.generateUserUUID(PROVIDER_1, USER_1); + assertTrue("User IDs should be equal", user1.equals(user2)); + + //check different name gives different ID + user1 = UUIDGenerator.generateUserUUID(PROVIDER_1, USER_1); + user2 = UUIDGenerator.generateUserUUID(PROVIDER_1, USER_2); + assertFalse("User IDs should not be equal", user1.equals(user2)); + + //check different provider gives different ID + user1 = UUIDGenerator.generateUserUUID(PROVIDER_1, USER_1); + user2 = UUIDGenerator.generateUserUUID(PROVIDER_2, USER_1); + assertFalse("User IDs should not be equal", user1.equals(user2)); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/VirtualHostTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/VirtualHostTest.java new file mode 100644 index 0000000000..ce213ee582 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/VirtualHostTest.java @@ -0,0 +1,147 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import junit.framework.TestCase; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.RecovererProvider; +import org.apache.qpid.server.configuration.startup.VirtualHostRecoverer; +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.logging.SystemOutMessageLogger; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.TestLogActor; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.TestMemoryMessageStore; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.StandardVirtualHostFactory; + +public class VirtualHostTest extends TestCase +{ + + private Broker _broker; + private StatisticsGatherer _statisticsGatherer; + private RecovererProvider _recovererProvider; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + CurrentActor.set(new TestLogActor(new SystemOutMessageLogger())); + + _broker = BrokerTestHelper.createBrokerMock(); + TaskExecutor taslExecutor = mock(TaskExecutor.class); + when(taslExecutor.isTaskExecutorThread()).thenReturn(true); + when(_broker.getTaskExecutor()).thenReturn(taslExecutor); + + _recovererProvider = mock(RecovererProvider.class); + _statisticsGatherer = mock(StatisticsGatherer.class); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + CurrentActor.remove(); + } + + public void testInitialisingState() + { + VirtualHost host = createHost(); + + assertEquals("Unexpected state", State.INITIALISING, host.getAttribute(VirtualHost.STATE)); + } + + public void testActiveState() + { + VirtualHost host = createHost(); + + host.setDesiredState(State.INITIALISING, State.ACTIVE); + assertEquals("Unexpected state", State.ACTIVE, host.getAttribute(VirtualHost.STATE)); + } + + public void testQuiescedState() + { + Map attributes = new HashMap(); + attributes.put(VirtualHost.NAME, getName()); + attributes.put(VirtualHost.TYPE, StandardVirtualHostFactory.TYPE); + attributes.put(VirtualHost.STORE_TYPE, TestMemoryMessageStore.TYPE); + attributes.put(VirtualHost.STATE, State.QUIESCED); + + VirtualHost host = createHost(attributes); + + assertEquals("Unexpected state", State.QUIESCED, host.getAttribute(VirtualHost.STATE)); + + host.setDesiredState(State.QUIESCED, State.ACTIVE); + assertEquals("Unexpected state", State.ACTIVE, host.getAttribute(VirtualHost.STATE)); + } + + public void testStoppedState() + { + VirtualHost host = createHost(); + + assertEquals("Unexpected state", State.INITIALISING, host.getAttribute(VirtualHost.STATE)); + + host.setDesiredState(State.INITIALISING, State.ACTIVE); + assertEquals("Unexpected state", State.ACTIVE, host.getAttribute(VirtualHost.STATE)); + + host.setDesiredState(State.ACTIVE, State.STOPPED); + assertEquals("Unexpected state", State.STOPPED, host.getAttribute(VirtualHost.STATE)); + } + + public void testDeletedState() + { + VirtualHost host = createHost(); + + assertEquals("Unexpected state", State.INITIALISING, host.getAttribute(VirtualHost.STATE)); + + host.setDesiredState(State.INITIALISING, State.DELETED); + assertEquals("Unexpected state", State.DELETED, host.getAttribute(VirtualHost.STATE)); + } + + private VirtualHost createHost() + { + Map attributes = new HashMap(); + attributes.put(VirtualHost.NAME, getName()); + attributes.put(VirtualHost.TYPE, StandardVirtualHostFactory.TYPE); + attributes.put(VirtualHost.STORE_TYPE, TestMemoryMessageStore.TYPE); + + VirtualHost host = createHost(attributes); + return host; + } + + private VirtualHost createHost(Map attributes) + { + ConfigurationEntry entry = new ConfigurationEntry(UUID.randomUUID(), VirtualHost.class.getSimpleName(), attributes, + Collections. emptySet(), null); + + return new VirtualHostRecoverer(_statisticsGatherer).create(_recovererProvider, entry, _broker); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactoryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactoryTest.java new file mode 100644 index 0000000000..ed22843e07 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/AuthenticationProviderFactoryTest.java @@ -0,0 +1,156 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.never; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import junit.framework.TestCase; + +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.PasswordCredentialManagingAuthenticationProvider; +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.plugin.QpidServiceLoader; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; + +public class AuthenticationProviderFactoryTest extends TestCase +{ + private PreferencesProviderCreator _preferencesProviderCreator = mock(PreferencesProviderCreator.class); + + public void testCreatePasswordCredentialManagingAuthenticationProvider() + { + AuthenticationManager am = mock(PrincipalDatabaseAuthenticationManager.class); + AuthenticationProvider provider = testForFactory(am, true); + assertTrue("The created provider should match the factory's AuthenticationManager type", + provider instanceof PasswordCredentialManagingAuthenticationProvider); + verify(am).onCreate(); + } + + public void testCreateNonPasswordCredentialManagingAuthenticationProvider() + { + AuthenticationManager am = mock(AuthenticationManager.class); + AuthenticationProvider provider = testForFactory(am, true); + assertFalse("The created provider should match the factory's AuthenticationManager type", + provider instanceof PasswordCredentialManagingAuthenticationProvider); + verify(am).onCreate(); + } + + public void testRecoverPasswordCredentialManagingAuthenticationProvider() + { + AuthenticationManager am = mock(PrincipalDatabaseAuthenticationManager.class); + AuthenticationProvider provider = testForFactory(am, false); + assertTrue("The created provider should match the factory's AuthenticationManager type", + provider instanceof PasswordCredentialManagingAuthenticationProvider); + verify(am, never()).onCreate(); + } + + public void testRecoverNonPasswordCredentialManagingAuthenticationProvider() + { + AuthenticationManager am = mock(AuthenticationManager.class); + AuthenticationProvider provider = testForFactory(am, false); + assertFalse("The created provider should match the factory's AuthenticationManager type", + provider instanceof PasswordCredentialManagingAuthenticationProvider); + verify(am, never()).onCreate(); + } + + @SuppressWarnings("unchecked") + private AuthenticationProvider testForFactory(AuthenticationManager authenticationManager, boolean create) + { + UUID id = UUID.randomUUID(); + Map attributes = new HashMap(); + + QpidServiceLoader authManagerFactoryServiceLoader = mock(QpidServiceLoader.class); + AuthenticationManagerFactory authenticationManagerFactory = mock(AuthenticationManagerFactory.class); + + Broker broker = mock(Broker.class); + + when(authManagerFactoryServiceLoader.atLeastOneInstanceOf(AuthenticationManagerFactory.class)).thenReturn( + Collections.singleton(authenticationManagerFactory)); + when(authenticationManagerFactory.createInstance(attributes)).thenReturn(authenticationManager); + + AuthenticationProviderFactory providerFactory = new AuthenticationProviderFactory(authManagerFactoryServiceLoader, _preferencesProviderCreator); + + AuthenticationProvider provider = null; + if (create) + { + provider = providerFactory.create(id, broker, attributes); + } + else + { + provider = providerFactory.recover(id, attributes, broker); + } + + assertNotNull("Provider is not created", provider); + assertEquals("Unexpected ID", id, provider.getId()); + + return provider; + } + + public void testCreatePasswordCredentialManagingAuthenticationProviderFailsWhenAnotherOneAlready() + { + Broker broker = mock(Broker.class); + PasswordCredentialManagingAuthenticationProvider anotherProvider = mock(PasswordCredentialManagingAuthenticationProvider.class); + when(broker.getAuthenticationProviders()).thenReturn(Collections.singleton(anotherProvider)); + + QpidServiceLoader loader = mock(QpidServiceLoader.class); + AuthenticationManagerFactory managerFactory = mock(AuthenticationManagerFactory.class); + when(managerFactory.createInstance(any(Map.class))).thenReturn(mock(PrincipalDatabaseAuthenticationManager.class)); + when(loader.atLeastOneInstanceOf(AuthenticationManagerFactory.class)).thenReturn(Collections.singleton(managerFactory)); + + AuthenticationProviderFactory providerFactory = new AuthenticationProviderFactory(loader, _preferencesProviderCreator); + + UUID randomUUID = UUID.randomUUID(); + AuthenticationProvider provider = providerFactory.create(randomUUID, broker, new HashMap()); + + assertNotNull("Provider is not created", provider); + assertEquals("Unexpected ID", randomUUID, provider.getId()); + } + + @SuppressWarnings("unchecked") + public void testCreateNonPasswordCredentialManagingAuthenticationProviderWhenAnotherOneAlreadyExist() + { + Broker broker = mock(Broker.class); + AuthenticationProvider anotherProvider = mock(AuthenticationProvider.class); + when(broker.getAuthenticationProviders()).thenReturn(Collections.singleton(anotherProvider)); + + QpidServiceLoader loader = mock(QpidServiceLoader.class); + AuthenticationManagerFactory managerFactory = mock(AuthenticationManagerFactory.class); + when(managerFactory.createInstance(any(Map.class))).thenReturn(mock(AuthenticationManager.class)); + when(loader.atLeastOneInstanceOf(AuthenticationManagerFactory.class)).thenReturn(Collections.singleton(managerFactory)); + + AuthenticationProviderFactory providerFactory = new AuthenticationProviderFactory(loader, _preferencesProviderCreator); + UUID id = UUID.randomUUID(); + AuthenticationProvider provider = providerFactory.create(id, broker, new HashMap()); + + assertNotNull("Provider is not created", provider); + assertEquals("Unexpected ID", id, provider.getId()); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderFactoryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderFactoryTest.java new file mode 100644 index 0000000000..64dfad94c3 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderFactoryTest.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.model.adapter; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.PreferencesProvider; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestFileUtils; + +public class FileSystemPreferencesProviderFactoryTest extends QpidTestCase +{ + private AuthenticationProvider _authenticationProvider; + private Broker _broker; + private FileSystemPreferencesProviderFactory _factory; + + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _authenticationProvider = mock(AuthenticationProvider.class); + _broker = BrokerTestHelper.createBrokerMock(); + when(_authenticationProvider.getParent(Broker.class)).thenReturn(_broker); + _factory = new FileSystemPreferencesProviderFactory(); + } + + public void tearDown() throws Exception + { + try + { + BrokerTestHelper.tearDown(); + } + finally + { + super.tearDown(); + } + } + + public void testGetType() + { + assertEquals(FileSystemPreferencesProvider.PROVIDER_TYPE, _factory.getType()); + } + + public void testCreateInstanceRecovering() + { + Map attributes = new HashMap(); + UUID id = UUID.randomUUID(); + attributes.put(PreferencesProvider.TYPE, FileSystemPreferencesProvider.class); + attributes.put(PreferencesProvider.NAME, "test-provider"); + File file = TestFileUtils.createTempFile(this, ".prefs.json", "{\"test_user\":{\"pref1\": \"pref1Value\", \"pref2\": 1.0} }"); + try + { + attributes.put(FileSystemPreferencesProvider.PATH, file.getAbsolutePath()); + PreferencesProvider provider = _factory.createInstance(id, attributes, _authenticationProvider); + assertNotNull("Preferences provider was not instantiated", provider); + assertEquals("Unexpected name", "test-provider", provider.getName()); + assertEquals("Unexpected id", id, provider.getId()); + assertEquals("Unexpected path", file.getAbsolutePath(), + provider.getAttribute(FileSystemPreferencesProvider.PATH)); + } + finally + { + file.delete(); + } + } + + public void testCreateInstanceRecoveringWhenPrefStoreDoesNotExist() + { + Map attributes = new HashMap(); + UUID id = UUID.randomUUID(); + attributes.put(PreferencesProvider.TYPE, FileSystemPreferencesProvider.class); + attributes.put(PreferencesProvider.NAME, "test-provider"); + File file = new File(TMP_FOLDER, UUID.randomUUID() + "prefs.json"); + assertFalse("Preferences store file should not exist", file.exists()); + try + { + attributes.put(FileSystemPreferencesProvider.PATH, file.getAbsolutePath()); + _factory.createInstance(id, attributes, _authenticationProvider); + } + catch (IllegalConfigurationException e) + { + // exception should be thrown if preferences store does not exist + } + } + + public void testCreateInstanceNotRecovering() + { + Map attributes = new HashMap(); + UUID id = UUID.randomUUID(); + attributes.put(PreferencesProvider.TYPE, FileSystemPreferencesProvider.class); + attributes.put(PreferencesProvider.NAME, "test-provider"); + File file = new File(TMP_FOLDER, UUID.randomUUID() + "prefs.json"); + assertFalse("Preferences store file should not exist", file.exists()); + try + { + attributes.put(FileSystemPreferencesProvider.PATH, file.getAbsolutePath()); + PreferencesProvider provider = _factory.createInstance(id, attributes, _authenticationProvider); + assertNotNull("Preferences provider was not recovered", provider); + assertEquals("Unexpected name", "test-provider", provider.getName()); + assertEquals("Unexpected id", id, provider.getId()); + assertEquals("Unexpected path", file.getAbsolutePath(), provider.getAttribute(FileSystemPreferencesProvider.PATH)); + assertTrue("Preferences store file should exist", file.exists()); + } + finally + { + file.delete(); + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderTest.java new file mode 100644 index 0000000000..0eab93541f --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/FileSystemPreferencesProviderTest.java @@ -0,0 +1,266 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.apache.qpid.server.configuration.updater.TaskExecutor; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.State; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.test.utils.TestFileUtils; + +public class FileSystemPreferencesProviderTest extends QpidTestCase +{ + private static final String TEST_PREFERENCES = "{\"user1\":{\"pref1\":\"pref1User1Value\", \"pref2\": true, \"pref3\": 1.0, \"pref4\": 2}," + + "\"user2\":{\"pref1\":\"pref1User2Value\", \"pref2\": false, \"pref3\": 2.0, \"pref4\": 3}}"; + private FileSystemPreferencesProvider _preferencesProvider; + private AuthenticationProvider _authenticationProvider; + private Broker _broker; + private String _user1, _user2; + private File _preferencesFile; + + protected void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + _authenticationProvider = mock(AuthenticationProvider.class); + _user1 = "user1"; + _user2 = "user2"; + _preferencesFile = TestFileUtils.createTempFile(this, ".prefs.json", TEST_PREFERENCES); + + _broker = BrokerTestHelper.createBrokerMock(); + TaskExecutor taslExecutor = mock(TaskExecutor.class); + when(taslExecutor.isTaskExecutorThread()).thenReturn(true); + when(_broker.getTaskExecutor()).thenReturn(taslExecutor); + when(_authenticationProvider.getParent(Broker.class)).thenReturn(_broker); + } + + protected void tearDown() throws Exception + { + try + { + BrokerTestHelper.tearDown(); + _preferencesFile.delete(); + } + finally + { + super.tearDown(); + } + } + + public void testConstructionWithExistingFile() + { + _preferencesProvider = createPreferencesProvider(); + assertEquals(State.INITIALISING, _preferencesProvider.getActualState()); + } + + public void testConstructionWithNonExistingFile() + { + File nonExistingFile = new File(TMP_FOLDER, "preferences-" + UUID.randomUUID() + ".json"); + assertFalse("Preferences file exists", nonExistingFile.exists()); + try + { + Map attributes = new HashMap(); + attributes.put(FileSystemPreferencesProvider.PATH, nonExistingFile.getAbsolutePath()); + _preferencesProvider = new FileSystemPreferencesProvider(UUID.randomUUID(), attributes, _authenticationProvider, _broker.getTaskExecutor()); + _preferencesProvider.createStoreIfNotExist(); + assertEquals(State.INITIALISING, _preferencesProvider.getActualState()); + assertTrue("Preferences file was not created", nonExistingFile.exists()); + } + finally + { + nonExistingFile.delete(); + } + } + + public void testConstructionWithEmptyFile() throws Exception + { + File emptyPrefsFile = new File(TMP_FOLDER, "preferences-" + UUID.randomUUID() + ".json"); + emptyPrefsFile.createNewFile(); + assertTrue("Preferences file does notexists", emptyPrefsFile.exists()); + try + { + Map attributes = new HashMap(); + attributes.put(FileSystemPreferencesProvider.PATH, emptyPrefsFile.getAbsolutePath()); + _preferencesProvider = new FileSystemPreferencesProvider(UUID.randomUUID(), attributes, _authenticationProvider, _broker.getTaskExecutor()); + assertEquals(State.INITIALISING, _preferencesProvider.getActualState()); + } + finally + { + emptyPrefsFile.delete(); + } + } + + public void testActivate() + { + _preferencesProvider = createPreferencesProvider(); + _preferencesProvider.setDesiredState(State.INITIALISING, State.ACTIVE); + + assertEquals("Unexpexpected state", State.ACTIVE, _preferencesProvider.getActualState()); + } + + public void testChangeAttributes() + { + _preferencesProvider = createPreferencesProvider(); + _preferencesProvider.setDesiredState(State.INITIALISING, State.ACTIVE); + + File newPrefsFile = TestFileUtils.createTempFile(this, ".prefs.json", "{\"user3\":{\"pref1\":\"pref1User3Value\", \"pref3\": 2.0}}"); + try + { + Map attributes = new HashMap(); + attributes.put(FileSystemPreferencesProvider.PATH, newPrefsFile.getAbsolutePath()); + _preferencesProvider.changeAttributes(attributes); + assertEquals("Unexpected path", newPrefsFile.getAbsolutePath(), + _preferencesProvider.getAttribute(FileSystemPreferencesProvider.PATH)); + + Map preferences1 = _preferencesProvider.getPreferences(_user1); + assertTrue("Unexpected preferences for user1", preferences1.isEmpty()); + + String user3 = "user3"; + Map preferences3 = _preferencesProvider.getPreferences(user3); + assertFalse("No preference found for user3", preferences3.isEmpty()); + assertEquals("Unexpected preference 1 for user 3", "pref1User3Value", preferences3.get("pref1")); + assertEquals("Unexpected preference 3 for user 3", 2.0, ((Number) preferences3.get("pref3")).floatValue(), 0.01); + } + finally + { + newPrefsFile.delete(); + } + } + + public void testGetPreferences() + { + _preferencesProvider = createPreferencesProvider(); + _preferencesProvider.setDesiredState(State.INITIALISING, State.ACTIVE); + + Map preferences1 = _preferencesProvider.getPreferences(_user1); + assertUser1Preferences(preferences1); + + Map preferences2 = _preferencesProvider.getPreferences(_user2); + assertUser2Preferences(preferences2); + + String user3 = "user3"; + Map preferences3 = _preferencesProvider.getPreferences(user3); + assertTrue("No preference found for user3", preferences3.isEmpty()); + } + + public void testSetPrefernces() + { + _preferencesProvider = createPreferencesProvider(); + _preferencesProvider.setDesiredState(State.INITIALISING, State.ACTIVE); + + Map newPreferences = new HashMap(); + newPreferences.put("pref2", false); + newPreferences.put("pref4", 8); + Map pref5 = new HashMap(); + pref5.put("test1", "test1Value"); + pref5.put("test2", 5); + newPreferences.put("pref5", pref5); + + _preferencesProvider.setPreferences(_user1, newPreferences); + + FileSystemPreferencesProvider newProvider = createPreferencesProvider(); + Map preferences1 = newProvider.getPreferences(_user1); + assertNotNull("Preferences should not be null for user 1", preferences1); + assertEquals("Unexpected preference 1 for user 1", "pref1User1Value", preferences1.get("pref1")); + assertEquals("Unexpected preference 2 for user 1", false, preferences1.get("pref2")); + assertEquals("Unexpected preference 3 for user 1", 1.0, ((Number) preferences1.get("pref3")).floatValue(), 0.01); + assertEquals("Unexpected preference 4 for user 1", 8, preferences1.get("pref4")); + assertNotNull("Unexpected preference 5 for user 1", preferences1.get("pref5")); + assertEquals("Unexpected preference 5 for user 1", pref5, preferences1.get("pref5")); + + Map preferences2 = newProvider.getPreferences(_user2); + assertUser2Preferences(preferences2); + + String user3 = "user3"; + Map preferences3 = newProvider.getPreferences(user3); + assertTrue("No preference found for user3", preferences3.isEmpty()); + } + + public void testDeletePrefernces() + { + _preferencesProvider = createPreferencesProvider(); + _preferencesProvider.setDesiredState(State.INITIALISING, State.ACTIVE); + + _preferencesProvider.deletePreferences(_user1); + + FileSystemPreferencesProvider newProvider = createPreferencesProvider(); + Map preferences1 = newProvider.getPreferences(_user1); + assertTrue("Preferences should not be set for user 1", preferences1.isEmpty()); + + Map preferences2 = newProvider.getPreferences(_user2); + assertUser2Preferences(preferences2); + + String user3 = "user3"; + Map preferences3 = newProvider.getPreferences(user3); + assertTrue("No preference found for user3", preferences3.isEmpty()); + } + + public void testListUserNames() + { + _preferencesProvider = createPreferencesProvider(); + _preferencesProvider.setDesiredState(State.INITIALISING, State.ACTIVE); + + Set userNames = _preferencesProvider.listUserIDs(); + + assertEquals("Unexpected user names", new HashSet(Arrays.asList("user1", "user2")), userNames); + } + + private FileSystemPreferencesProvider createPreferencesProvider() + { + Map attributes = new HashMap(); + attributes.put(FileSystemPreferencesProvider.PATH, _preferencesFile.getAbsolutePath()); + attributes.put(FileSystemPreferencesProvider.NAME, "test"); + return _preferencesProvider = new FileSystemPreferencesProvider(UUID.randomUUID(), attributes, _authenticationProvider, _broker.getTaskExecutor()); + } + + private void assertUser1Preferences(Map preferences1) + { + assertNotNull("Preferences should not be null for user 1", preferences1); + assertEquals("Unexpected preference 1 for user 1", "pref1User1Value", preferences1.get("pref1")); + assertEquals("Unexpected preference 2 for user 1", true, preferences1.get("pref2")); + assertEquals("Unexpected preference 3 for user 1", 1.0, ((Number) preferences1.get("pref3")).floatValue(), 0.01); + assertEquals("Unexpected preference 4 for user 1", 2, preferences1.get("pref4")); + assertNull("Unexpected preference 5 for user 1", preferences1.get("pref5")); + } + + private void assertUser2Preferences(Map preferences2) + { + assertNotNull("Preferences should not be null for user 2", preferences2); + assertEquals("Unexpected preference 1 for user 2", "pref1User2Value", preferences2.get("pref1")); + assertEquals("Unexpected preference 2 for user 2", false, preferences2.get("pref2")); + assertEquals("Unexpected preference 2 for user 2", 2.0, ((Number) preferences2.get("pref3")).floatValue(), 0.01); + assertEquals("Unexpected preference 3 for user 2", 3, preferences2.get("pref4")); + assertNull("Unexpected preference 5 for user 2", preferences2.get("pref5")); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java new file mode 100644 index 0000000000..54826b8c88 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/adapter/PortFactoryTest.java @@ -0,0 +1,389 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.model.adapter; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.any; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.model.AuthenticationProvider; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.KeyStore; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Protocol; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.model.TrustStore; +import org.apache.qpid.test.utils.QpidTestCase; + +public class PortFactoryTest extends QpidTestCase +{ + private UUID _portId = UUID.randomUUID(); + private int _portNumber = 123; + private Set _tcpStringSet = Collections.singleton(Transport.TCP.name()); + private Set _tcpTransportSet = Collections.singleton(Transport.TCP); + private Set _sslStringSet = Collections.singleton(Transport.SSL.name()); + private Set _sslTransportSet = Collections.singleton(Transport.SSL); + + private Map _attributes = new HashMap(); + + private Broker _broker = mock(Broker.class); + private KeyStore _keyStore = mock(KeyStore.class); + private TrustStore _trustStore = mock(TrustStore.class); + private String _authProviderName = "authProvider"; + private AuthenticationProvider _authProvider = mock(AuthenticationProvider.class); + private PortFactory _portFactory; + + @Override + protected void setUp() throws Exception + { + when(_broker.findAuthenticationProviderByName(_authProviderName)).thenReturn(_authProvider); + + setTestSystemProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_EXCLUDES, null); + setTestSystemProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_INCLUDES, null); + _portFactory = new PortFactory(); + + _attributes.put(Port.PORT, _portNumber); + _attributes.put(Port.TRANSPORTS, _tcpStringSet); + _attributes.put(Port.AUTHENTICATION_PROVIDER, _authProviderName); + _attributes.put(Port.TCP_NO_DELAY, "true"); + _attributes.put(Port.RECEIVE_BUFFER_SIZE, "1"); + _attributes.put(Port.SEND_BUFFER_SIZE, "2"); + _attributes.put(Port.BINDING_ADDRESS, "127.0.0.1"); + } + + public void testDefaultProtocols() + { + Collection protocols = _portFactory.getDefaultProtocols(); + EnumSet expected = EnumSet.of(Protocol.AMQP_0_8, Protocol.AMQP_0_9, Protocol.AMQP_0_9_1, Protocol.AMQP_0_10, + Protocol.AMQP_1_0); + assertEquals("Unexpected protocols", new HashSet(expected), new HashSet(protocols)); + } + + public void testDefaultProtocolsWhenProtocolExcludeSystemPropertyIsSet() + { + setTestSystemProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_EXCLUDES, Protocol.AMQP_1_0.name() + "," + + Protocol.AMQP_0_10.name()); + _portFactory = new PortFactory(); + Collection protocols = _portFactory.getDefaultProtocols(); + EnumSet expected = EnumSet.of(Protocol.AMQP_0_8, Protocol.AMQP_0_9, Protocol.AMQP_0_9_1); + assertEquals("Unexpected protocols", new HashSet(expected), new HashSet(protocols)); + } + + public void testDefaultProtocolsWhenProtocolIncludeSystemPropertyIsSet() + { + setTestSystemProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_EXCLUDES, Protocol.AMQP_1_0.name() + "," + + Protocol.AMQP_0_10.name() + "," + Protocol.AMQP_0_9_1.name()); + setTestSystemProperty(BrokerProperties.PROPERTY_BROKER_DEFAULT_AMQP_PROTOCOL_INCLUDES, Protocol.AMQP_0_10.name() + "," + + Protocol.AMQP_0_9_1.name()); + _portFactory = new PortFactory(); + Collection protocols = _portFactory.getDefaultProtocols(); + EnumSet expected = EnumSet.of(Protocol.AMQP_0_8, Protocol.AMQP_0_9, Protocol.AMQP_0_9_1, Protocol.AMQP_0_10); + assertEquals("Unexpected protocols", new HashSet(expected), new HashSet(protocols)); + } + + public void testCreatePortWithMinimumAttributes() + { + Map attributes = new HashMap(); + attributes.put(Port.PORT, 1); + attributes.put(Port.AUTHENTICATION_PROVIDER, _authProviderName); + Port port = _portFactory.createPort(_portId, _broker, attributes); + + assertNotNull(port); + assertTrue(port instanceof AmqpPortAdapter); + assertEquals("Unexpected port", 1, port.getPort()); + assertEquals("Unexpected transports", Collections.singleton(PortFactory.DEFAULT_TRANSPORT), port.getTransports()); + assertEquals("Unexpected protocols", _portFactory.getDefaultProtocols(), port.getProtocols()); + assertEquals("Unexpected send buffer size", PortFactory.DEFAULT_AMQP_SEND_BUFFER_SIZE, + port.getAttribute(Port.SEND_BUFFER_SIZE)); + assertEquals("Unexpected receive buffer size", PortFactory.DEFAULT_AMQP_RECEIVE_BUFFER_SIZE, + port.getAttribute(Port.RECEIVE_BUFFER_SIZE)); + assertEquals("Unexpected need client auth", PortFactory.DEFAULT_AMQP_NEED_CLIENT_AUTH, + port.getAttribute(Port.NEED_CLIENT_AUTH)); + assertEquals("Unexpected want client auth", PortFactory.DEFAULT_AMQP_WANT_CLIENT_AUTH, + port.getAttribute(Port.WANT_CLIENT_AUTH)); + assertEquals("Unexpected tcp no delay", PortFactory.DEFAULT_AMQP_TCP_NO_DELAY, port.getAttribute(Port.TCP_NO_DELAY)); + assertEquals("Unexpected binding", PortFactory.DEFAULT_AMQP_BINDING, port.getAttribute(Port.BINDING_ADDRESS)); + } + + public void testCreateAmqpPort() + { + createAmqpPortTestImpl(false, false, false, null, null); + } + + public void testCreateAmqpPortUsingSslFailsWithoutKeyStore() + { + try + { + createAmqpPortTestImpl(true, false, false, null, null); + fail("expected exception due to lack of SSL keystore"); + } + catch(IllegalConfigurationException e) + { + //expected + } + } + + public void testCreateAmqpPortUsingSslSucceedsWithKeyStore() + { + String keyStoreName = "myKeyStore"; + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); + + createAmqpPortTestImpl(true, false, false, keyStoreName, null); + } + + public void testCreateAmqpPortNeedingClientAuthFailsWithoutTrustStore() + { + String keyStoreName = "myKeyStore"; + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); + + when(_broker.findTrustStoreByName(any(String.class))).thenReturn(null); + + try + { + createAmqpPortTestImpl(true, true, false, keyStoreName, null); + fail("expected exception due to lack of SSL truststore"); + } + catch(IllegalConfigurationException e) + { + //expected + } + } + + public void testCreateAmqpPortNeedingClientAuthSucceedsWithTrustStore() + { + String keyStoreName = "myKeyStore"; + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); + + String trustStoreName = "myTrustStore"; + when(_broker.findTrustStoreByName(trustStoreName)).thenReturn(_trustStore); + + createAmqpPortTestImpl(true, true, false, keyStoreName, new String[]{trustStoreName}); + } + + public void testCreateAmqpPortWantingClientAuthFailsWithoutTrustStore() + { + String keyStoreName = "myKeyStore"; + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); + + try + { + createAmqpPortTestImpl(true, false, true, keyStoreName, null); + fail("expected exception due to lack of SSL truststore"); + } + catch(IllegalConfigurationException e) + { + //expected + } + } + + public void testCreateAmqpPortWantingClientAuthSucceedsWithTrustStore() + { + String keyStoreName = "myKeyStore"; + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); + + String trustStoreName = "myTrustStore"; + when(_broker.findTrustStoreByName(trustStoreName)).thenReturn(_trustStore); + + createAmqpPortTestImpl(true, false, true, keyStoreName, new String[]{trustStoreName}); + } + + public void createAmqpPortTestImpl(boolean useSslTransport, boolean needClientAuth, boolean wantClientAuth, + String keystoreName, String[] trustStoreNames) + { + Set amqp010ProtocolSet = Collections.singleton(Protocol.AMQP_0_10); + Set amqp010StringSet = Collections.singleton(Protocol.AMQP_0_10.name()); + _attributes.put(Port.PROTOCOLS, amqp010StringSet); + + if(useSslTransport) + { + _attributes.put(Port.TRANSPORTS, _sslStringSet); + } + + if(needClientAuth) + { + _attributes.put(Port.NEED_CLIENT_AUTH, "true"); + } + + if(wantClientAuth) + { + _attributes.put(Port.WANT_CLIENT_AUTH, "true"); + } + + if(keystoreName != null) + { + _attributes.put(Port.KEY_STORE, keystoreName); + } + + if(trustStoreNames != null) + { + _attributes.put(Port.TRUST_STORES, Arrays.asList(trustStoreNames)); + } + + Port port = _portFactory.createPort(_portId, _broker, _attributes); + + assertNotNull(port); + assertTrue(port instanceof AmqpPortAdapter); + assertEquals(_portId, port.getId()); + assertEquals(_portNumber, port.getPort()); + if(useSslTransport) + { + assertEquals(_sslTransportSet, port.getTransports()); + } + else + { + assertEquals(_tcpTransportSet, port.getTransports()); + } + assertEquals(amqp010ProtocolSet, port.getProtocols()); + assertEquals("Unexpected send buffer size", 2, port.getAttribute(Port.SEND_BUFFER_SIZE)); + assertEquals("Unexpected receive buffer size", 1, port.getAttribute(Port.RECEIVE_BUFFER_SIZE)); + assertEquals("Unexpected need client auth", needClientAuth, port.getAttribute(Port.NEED_CLIENT_AUTH)); + assertEquals("Unexpected want client auth", wantClientAuth, port.getAttribute(Port.WANT_CLIENT_AUTH)); + assertEquals("Unexpected tcp no delay", true, port.getAttribute(Port.TCP_NO_DELAY)); + assertEquals("Unexpected binding", "127.0.0.1", port.getAttribute(Port.BINDING_ADDRESS)); + } + + public void testCreateNonAmqpPort() + { + Set nonAmqpProtocolSet = Collections.singleton(Protocol.JMX_RMI); + Set nonAmqpStringSet = Collections.singleton(Protocol.JMX_RMI.name()); + _attributes = new HashMap(); + _attributes.put(Port.PROTOCOLS, nonAmqpStringSet); + _attributes.put(Port.AUTHENTICATION_PROVIDER, _authProviderName); + _attributes.put(Port.PORT, _portNumber); + _attributes.put(Port.TRANSPORTS, _tcpStringSet); + + Port port = _portFactory.createPort(_portId, _broker, _attributes); + + assertNotNull(port); + assertFalse("Port should be a PortAdapter, not its AMQP-specific subclass", port instanceof AmqpPortAdapter); + assertEquals(_portId, port.getId()); + assertEquals(_portNumber, port.getPort()); + assertEquals(_tcpTransportSet, port.getTransports()); + assertEquals(nonAmqpProtocolSet, port.getProtocols()); + assertNull("Unexpected send buffer size", port.getAttribute(Port.SEND_BUFFER_SIZE)); + assertNull("Unexpected receive buffer size", port.getAttribute(Port.RECEIVE_BUFFER_SIZE)); + assertNull("Unexpected need client auth", port.getAttribute(Port.NEED_CLIENT_AUTH)); + assertNull("Unexpected want client auth", port.getAttribute(Port.WANT_CLIENT_AUTH)); + assertNull("Unexpected tcp no delay", port.getAttribute(Port.TCP_NO_DELAY)); + assertNull("Unexpected binding", port.getAttribute(Port.BINDING_ADDRESS)); + } + + public void testCreateNonAmqpPortWithPartiallySetAttributes() + { + Set nonAmqpProtocolSet = Collections.singleton(Protocol.JMX_RMI); + Set nonAmqpStringSet = Collections.singleton(Protocol.JMX_RMI.name()); + _attributes = new HashMap(); + _attributes.put(Port.PROTOCOLS, nonAmqpStringSet); + _attributes.put(Port.AUTHENTICATION_PROVIDER, _authProviderName); + _attributes.put(Port.PORT, _portNumber); + + Port port = _portFactory.createPort(_portId, _broker, _attributes); + + assertNotNull(port); + assertFalse("Port should be a PortAdapter, not its AMQP-specific subclass", port instanceof AmqpPortAdapter); + assertEquals(_portId, port.getId()); + assertEquals(_portNumber, port.getPort()); + assertEquals(Collections.singleton(PortFactory.DEFAULT_TRANSPORT), port.getTransports()); + assertEquals(nonAmqpProtocolSet, port.getProtocols()); + assertNull("Unexpected send buffer size", port.getAttribute(Port.SEND_BUFFER_SIZE)); + assertNull("Unexpected receive buffer size", port.getAttribute(Port.RECEIVE_BUFFER_SIZE)); + assertNull("Unexpected need client auth", port.getAttribute(Port.NEED_CLIENT_AUTH)); + assertNull("Unexpected want client auth", port.getAttribute(Port.WANT_CLIENT_AUTH)); + assertNull("Unexpected tcp no delay", port.getAttribute(Port.TCP_NO_DELAY)); + assertNull("Unexpected binding", port.getAttribute(Port.BINDING_ADDRESS)); + } + + public void testCreateMixedAmqpAndNonAmqpThrowsException() + { + Set mixedProtocolSet = new HashSet(Arrays.asList(Protocol.AMQP_0_10.name(), Protocol.JMX_RMI.name())); + _attributes.put(Port.PROTOCOLS, mixedProtocolSet); + + try + { + _portFactory.createPort(_portId, _broker, _attributes); + fail("Exception not thrown"); + } + catch (IllegalConfigurationException e) + { + // pass + } + } + + public void testCreateRMIPortWhenAnotherRMIPortAlreadyExists() + { + Map attributes = new HashMap(); + attributes.put(Port.PORT, 1); + attributes.put(Port.NAME, getTestName()); + attributes.put(Port.TRANSPORTS, Collections.singleton(Transport.TCP)); + attributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.RMI)); + + Port rmiPort = mock(Port.class); + when(rmiPort.getProtocols()).thenReturn(Collections.singleton(Protocol.RMI)); + when(_broker.getPorts()).thenReturn(Collections.singletonList(rmiPort)); + + try + { + _portFactory.createPort(_portId, _broker, attributes); + fail("RMI port creation should fail as another one olready exist"); + } + catch(IllegalConfigurationException e) + { + // pass + } + } + + public void testCreateRMIPortRequestingSslFails() + { + String keyStoreName = "myKeyStore"; + + Map attributes = new HashMap(); + attributes.put(Port.PORT, 1); + attributes.put(Port.NAME, getTestName()); + attributes.put(Port.TRANSPORTS, Collections.singleton(Transport.SSL)); + attributes.put(Port.PROTOCOLS, Collections.singleton(Protocol.RMI)); + _attributes.put(Port.KEY_STORE, keyStoreName); + + when(_broker.findKeyStoreByName(keyStoreName)).thenReturn(_keyStore); + + try + { + _portFactory.createPort(_portId, _broker, attributes); + fail("RMI port creation should fail due to requesting SSL"); + } + catch(IllegalConfigurationException e) + { + e.printStackTrace(); + // pass + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/configuration/ConfigurationEntryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/configuration/ConfigurationEntryTest.java new file mode 100644 index 0000000000..dd48d7b56d --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/model/configuration/ConfigurationEntryTest.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.model.configuration; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import junit.framework.TestCase; + +import org.apache.qpid.server.configuration.ConfigurationEntry; +import org.apache.qpid.server.configuration.ConfigurationEntryStore; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.VirtualHost; + +public class ConfigurationEntryTest extends TestCase +{ + public void testGetChildren() + { + ConfigurationEntryStore store = mock(ConfigurationEntryStore.class); + + ConfigurationEntry virtualHostEntry1 = new ConfigurationEntry(UUID.randomUUID(), VirtualHost.class.getSimpleName(), + Collections. emptyMap(), Collections. emptySet(), store); + ConfigurationEntry virtualHostEntry2 = new ConfigurationEntry(UUID.randomUUID(), VirtualHost.class.getSimpleName(), + Collections. emptyMap(), Collections. emptySet(), store); + ConfigurationEntry portEntry = new ConfigurationEntry(UUID.randomUUID(), Port.class.getSimpleName(), + Collections. emptyMap(), Collections. emptySet(), store); + + when(store.getEntry(virtualHostEntry1.getId())).thenReturn(virtualHostEntry1); + when(store.getEntry(virtualHostEntry2.getId())).thenReturn(virtualHostEntry2); + when(store.getEntry(portEntry.getId())).thenReturn(portEntry); + + Set childrenIds = new HashSet(); + childrenIds.add(virtualHostEntry1.getId()); + childrenIds.add(virtualHostEntry2.getId()); + childrenIds.add(portEntry.getId()); + ConfigurationEntry parentEntry = new ConfigurationEntry(UUID.randomUUID(), Broker.class.getSimpleName(), + Collections. emptyMap(), childrenIds, store); + + Map> children = parentEntry.getChildren(); + assertNotNull("Null is returned for children", children); + assertEquals("Unexpected size", 2, children.size()); + Collection virtualHosts = children.get(VirtualHost.class.getSimpleName()); + Collection ports = children.get(Port.class.getSimpleName()); + assertEquals("Unexpected virtual hosts", + new HashSet(Arrays.asList(virtualHostEntry1, virtualHostEntry2)), + new HashSet(virtualHosts)); + assertEquals("Unexpected ports", new HashSet(Arrays.asList(portEntry)), + new HashSet(ports)); + } + + public void testHashCode() + { + ConfigurationEntryStore store = mock(ConfigurationEntryStore.class); + + UUID id = UUID.randomUUID(); + ConfigurationEntry entry1 = new ConfigurationEntry(id, VirtualHost.class.getSimpleName(), + Collections. emptyMap(), Collections.singleton(UUID.randomUUID()), store); + ConfigurationEntry entry2 = new ConfigurationEntry(id, VirtualHost.class.getSimpleName(), + Collections. emptyMap(), Collections.singleton(UUID.randomUUID()), store); + ConfigurationEntry entryWithDifferentId = new ConfigurationEntry(UUID.randomUUID(), + VirtualHost.class.getSimpleName(), Collections. emptyMap(), Collections.singleton(UUID.randomUUID()), store); + + assertTrue(entry1.hashCode() == entry2.hashCode()); + assertFalse(entry1.hashCode() == entryWithDifferentId.hashCode()); + } + + public void testEqualsObject() + { + ConfigurationEntryStore store = mock(ConfigurationEntryStore.class); + + UUID id = UUID.randomUUID(); + Map attributes1 = new HashMap(); + attributes1.put(VirtualHost.NAME, "name1"); + Set childrenIds = Collections.singleton(UUID.randomUUID()); + ConfigurationEntry entry1 = new ConfigurationEntry(id, VirtualHost.class.getSimpleName(), attributes1, + childrenIds, store); + + Map attributes2 = new HashMap(); + attributes2.put(VirtualHost.NAME, "name2"); + + ConfigurationEntry entry2 = new ConfigurationEntry(id, VirtualHost.class.getSimpleName(), attributes1, + childrenIds, store); + ConfigurationEntry entryWithDifferentId = new ConfigurationEntry(UUID.randomUUID(), + VirtualHost.class.getSimpleName(), attributes1, childrenIds, store); + + assertTrue(entry1.equals(entry2)); + assertFalse("Entries should be diferrent because of diferrent IDs", entry1.equals(entryWithDifferentId)); + + ConfigurationEntry entryWithDifferentChildId = new ConfigurationEntry(id, + VirtualHost.class.getSimpleName(), attributes1, Collections.singleton(UUID.randomUUID()), store); + assertFalse("Entries should be diferrent because of diferrent children", entry1.equals(entryWithDifferentChildId)); + + ConfigurationEntry entryWithDifferentName = new ConfigurationEntry(id, + VirtualHost.class.getSimpleName(), attributes2, childrenIds, store); + assertFalse("Entries should be diferrent because of diferrent attributes", entry1.equals(entryWithDifferentName)); + + ConfigurationEntry entryWithDifferentType = new ConfigurationEntry(id, + Broker.class.getSimpleName(), attributes1, childrenIds, store); + assertFalse("Entries should be diferrent because of diferrent types", entry1.equals(entryWithDifferentType)); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java new file mode 100644 index 0000000000..a468fa072b --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/AMQPriorityQueueTest.java @@ -0,0 +1,112 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.queue; + +import java.util.Collections; +import junit.framework.AssertionFailedError; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.ServerMessage; + +import java.util.ArrayList; +import org.apache.qpid.server.model.Queue; + +import static org.mockito.Mockito.when; + +public class AMQPriorityQueueTest extends SimpleAMQQueueTest +{ + + @Override + public void setUp() throws Exception + { + setArguments(Collections.singletonMap(Queue.PRIORITIES,(Object)3)); + super.setUp(); + } + + public void testPriorityOrdering() throws AMQException, InterruptedException + { + + // Enqueue messages in order + SimpleAMQQueue queue = getQueue(); + queue.enqueue(createMessage(1L, (byte) 10)); + queue.enqueue(createMessage(2L, (byte) 4)); + queue.enqueue(createMessage(3L, (byte) 0)); + + // Enqueue messages in reverse order + queue.enqueue(createMessage(4L, (byte) 0)); + queue.enqueue(createMessage(5L, (byte) 4)); + queue.enqueue(createMessage(6L, (byte) 10)); + + // Enqueue messages out of order + queue.enqueue(createMessage(7L, (byte) 4)); + queue.enqueue(createMessage(8L, (byte) 10)); + queue.enqueue(createMessage(9L, (byte) 0)); + + // Register subscriber + queue.registerSubscription(getSubscription(), false); + Thread.sleep(150); + + ArrayList msgs = getSubscription().getMessages(); + try + { + assertEquals(1L, msgs.get(0).getMessage().getMessageNumber()); + assertEquals(6L, msgs.get(1).getMessage().getMessageNumber()); + assertEquals(8L, msgs.get(2).getMessage().getMessageNumber()); + + assertEquals(2L, msgs.get(3).getMessage().getMessageNumber()); + assertEquals(5L, msgs.get(4).getMessage().getMessageNumber()); + assertEquals(7L, msgs.get(5).getMessage().getMessageNumber()); + + assertEquals(3L, msgs.get(6).getMessage().getMessageNumber()); + assertEquals(4L, msgs.get(7).getMessage().getMessageNumber()); + assertEquals(9L, msgs.get(8).getMessage().getMessageNumber()); + } + catch (AssertionFailedError afe) + { + // Show message order on failure. + int index = 1; + for (QueueEntry qe : msgs) + { + System.err.println(index + ":" + qe.getMessage().getMessageNumber()); + index++; + } + + throw afe; + } + + } + + protected ServerMessage createMessage(Long id, byte i) throws AMQException + { + + ServerMessage msg = super.createMessage(id); + AMQMessageHeader hdr = msg.getMessageHeader(); + when(hdr.getPriority()).thenReturn(i); + return msg; + } + + protected ServerMessage createMessage(Long id) throws AMQException + { + return createMessage(id, (byte) 0); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java new file mode 100644 index 0000000000..9a2c5bc166 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/AMQQueueFactoryTest.java @@ -0,0 +1,560 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.queue; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.server.configuration.BrokerProperties; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.exchange.DefaultExchangeFactory; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class AMQQueueFactoryTest extends QpidTestCase +{ + private QueueRegistry _queueRegistry; + private VirtualHost _virtualHost; + private AMQQueueFactory _queueFactory; + private List _queues; + private QueueConfiguration _queueConfiguration; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _queues = new ArrayList(); + + _virtualHost = mock(VirtualHost.class); + + VirtualHostConfiguration vhostConfig = mock(VirtualHostConfiguration.class); + when(_virtualHost.getConfiguration()).thenReturn(vhostConfig); + _queueConfiguration = mock(QueueConfiguration.class); + when(vhostConfig.getQueueConfiguration(anyString())).thenReturn(_queueConfiguration); + LogActor logActor = mock(LogActor.class); + CurrentActor.set(logActor); + RootMessageLogger rootLogger = mock(RootMessageLogger.class); + when(logActor.getRootMessageLogger()).thenReturn(rootLogger); + DurableConfigurationStore store = mock(DurableConfigurationStore.class); + when(_virtualHost.getDurableConfigurationStore()).thenReturn(store); + + mockExchangeCreation(); + mockQueueRegistry(); + delegateVhostQueueCreation(); + + when(_virtualHost.getQueues()).thenReturn(_queues); + + + _queueFactory = new AMQQueueFactory(_virtualHost, _queueRegistry); + + + + } + + private void delegateVhostQueueCreation() throws AMQException + { + final ArgumentCaptor id = ArgumentCaptor.forClass(UUID.class); + final ArgumentCaptor queueName = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor durable = ArgumentCaptor.forClass(Boolean.class); + final ArgumentCaptor owner = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor autoDelete = ArgumentCaptor.forClass(Boolean.class); + final ArgumentCaptor exclusive = ArgumentCaptor.forClass(Boolean.class); + final ArgumentCaptor deleteOnNoConsumer = ArgumentCaptor.forClass(Boolean.class); + final ArgumentCaptor arguments = ArgumentCaptor.forClass(Map.class); + + when(_virtualHost.createQueue(id.capture(), queueName.capture(), durable.capture(), owner.capture(), + autoDelete.capture(), exclusive.capture(), deleteOnNoConsumer.capture(), arguments.capture())).then( + new Answer() + { + @Override + public AMQQueue answer(InvocationOnMock invocation) throws Throwable + { + return _queueFactory.createQueue(id.getValue(), + queueName.getValue(), + durable.getValue(), + owner.getValue(), + autoDelete.getValue(), + exclusive.getValue(), + deleteOnNoConsumer.getValue(), + arguments.getValue()); + } + } + ); + } + + private void mockQueueRegistry() + { + _queueRegistry = mock(QueueRegistry.class); + + final ArgumentCaptor capturedQueue = ArgumentCaptor.forClass(AMQQueue.class); + doAnswer(new Answer() + { + + @Override + public Object answer(final InvocationOnMock invocation) throws Throwable + { + AMQQueue queue = capturedQueue.getValue(); + when(_queueRegistry.getQueue(eq(queue.getId()))).thenReturn(queue); + when(_queueRegistry.getQueue(eq(queue.getName()))).thenReturn(queue); + when(_virtualHost.getQueue(eq(queue.getId()))).thenReturn(queue); + when(_virtualHost.getQueue(eq(queue.getName()))).thenReturn(queue); + _queues.add(queue); + + return null; + } + }).when(_queueRegistry).registerQueue(capturedQueue.capture()); + } + + private void mockExchangeCreation() throws AMQException + { + final ArgumentCaptor idCapture = ArgumentCaptor.forClass(UUID.class); + final ArgumentCaptor exchangeNameCapture = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor type = ArgumentCaptor.forClass(String.class); + + when(_virtualHost.createExchange(idCapture.capture(), exchangeNameCapture.capture(), type.capture(), + anyBoolean(), anyBoolean(), anyString())).then( + new Answer() + { + @Override + public Exchange answer(InvocationOnMock invocation) throws Throwable + { + final String name = exchangeNameCapture.getValue(); + final UUID id = idCapture.getValue(); + + final Exchange exchange = mock(Exchange.class); + ExchangeType exType = mock(ExchangeType.class); + + when(exchange.getName()).thenReturn(name); + when(exchange.getId()).thenReturn(id); + when(exchange.getType()).thenReturn(exType); + + final String typeName = type.getValue(); + when(exType.getType()).thenReturn(typeName); + when(exchange.getTypeName()).thenReturn(typeName); + + when(_virtualHost.getExchange(eq(name))).thenReturn(exchange); + when(_virtualHost.getExchange(eq(id))).thenReturn(exchange); + + final ArgumentCaptor queue = ArgumentCaptor.forClass(AMQQueue.class); + + when(exchange.addBinding(anyString(),queue.capture(),anyMap())).then(new Answer() { + + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable + { + when(exchange.isBound(eq(queue.getValue()))).thenReturn(true); + return true; + } + }); + + return exchange; + } + } + ); + } + + @Override + public void tearDown() throws Exception + { + super.tearDown(); + } + + private void verifyRegisteredQueueCount(int count) + { + assertEquals("Queue was not registered in virtualhost", count, _virtualHost.getQueues().size()); + } + + + private void verifyQueueRegistered(String queueName) + { + assertNotNull("Queue " + queueName + " was not created", _virtualHost.getQueue(queueName)); + } + + public void testPriorityQueueRegistration() throws Exception + { + Map attributes = Collections.singletonMap(Queue.PRIORITIES, (Object) 5); + + + AMQQueue queue = _queueFactory.createQueue(UUIDGenerator.generateRandomUUID(), + "testPriorityQueue", + false, + "owner", + false, + false, + false, + attributes); + + assertEquals("Queue not a priorty queue", AMQPriorityQueue.class, queue.getClass()); + verifyQueueRegistered("testPriorityQueue"); + verifyRegisteredQueueCount(1); + } + + + public void testSimpleQueueRegistration() throws Exception + { + String queueName = getName(); + String dlQueueName = queueName + AMQQueueFactory.DEFAULT_DLQ_NAME_SUFFIX; + + AMQQueue queue = _queueFactory.createQueue(UUIDGenerator.generateRandomUUID(), queueName, false, "owner", false, + false, + false, + null); + assertEquals("Queue not a simple queue", SimpleAMQQueue.class, queue.getClass()); + verifyQueueRegistered(queueName); + + //verify that no alternate exchange or DLQ were produced + + assertNull("Queue should not have an alternate exchange as DLQ wasnt enabled", queue.getAlternateExchange()); + assertNull("The DLQ should not exist", _virtualHost.getQueue(dlQueueName)); + + verifyRegisteredQueueCount(1); + } + + /** + * Tests that setting the {@link QueueArgumentsConverter#X_QPID_DLQ_ENABLED} argument true does + * cause the alternate exchange to be set and DLQ to be produced. + * @throws AMQException + */ + public void testDeadLetterQueueEnabled() throws AMQException + { + Map attributes = Collections.singletonMap(Queue.CREATE_DLQ_ON_CREATION, (Object) true); + + String queueName = "testDeadLetterQueueEnabled"; + String dlExchangeName = queueName + DefaultExchangeFactory.DEFAULT_DLE_NAME_SUFFIX; + String dlQueueName = queueName + AMQQueueFactory.DEFAULT_DLQ_NAME_SUFFIX; + + assertNull("The DLQ should not yet exist", _virtualHost.getQueue(dlQueueName)); + assertNull("The alternate exchange should not yet exist", _virtualHost.getExchange(dlExchangeName)); + + AMQQueue queue = _queueFactory.createQueue(UUIDGenerator.generateRandomUUID(), + queueName, + false, + "owner", + false, + false, + false, + attributes); + + Exchange altExchange = queue.getAlternateExchange(); + assertNotNull("Queue should have an alternate exchange as DLQ is enabled", altExchange); + assertEquals("Alternate exchange name was not as expected", dlExchangeName, altExchange.getName()); + assertEquals("Alternate exchange type was not as expected", ExchangeDefaults.FANOUT_EXCHANGE_CLASS, altExchange.getTypeName()); + + assertNotNull("The alternate exchange was not registered as expected", _virtualHost.getExchange(dlExchangeName)); + assertEquals("The registered exchange was not the expected exchange instance", altExchange, _virtualHost.getExchange(dlExchangeName)); + + AMQQueue dlQueue = _virtualHost.getQueue(dlQueueName); + assertNotNull("The DLQ was not registered as expected", dlQueue); + assertTrue("DLQ should have been bound to the alternate exchange", altExchange.isBound(dlQueue)); + assertNull("DLQ should have no alternate exchange", dlQueue.getAlternateExchange()); + assertEquals("DLQ should have a zero maximum delivery count", 0, dlQueue.getMaximumDeliveryCount()); + + //2 queues should have been registered + verifyRegisteredQueueCount(2); + } + + /** + * Tests that the deadLetterQueues/maximumDeliveryCount settings from the configuration + * are not applied to the DLQ itself. + * @throws AMQException + */ + public void testDeadLetterQueueDoesNotInheritDLQorMDCSettings() throws Exception + { + + String queueName = "testDeadLetterQueueEnabled"; + String dlExchangeName = queueName + DefaultExchangeFactory.DEFAULT_DLE_NAME_SUFFIX; + String dlQueueName = queueName + AMQQueueFactory.DEFAULT_DLQ_NAME_SUFFIX; + + when(_queueConfiguration.getMaxDeliveryCount()).thenReturn(5); + when(_queueConfiguration.isDeadLetterQueueEnabled()).thenReturn(true); + + assertNull("The DLQ should not yet exist", _virtualHost.getQueue(dlQueueName)); + assertNull("The alternate exchange should not yet exist", _virtualHost.getExchange(dlExchangeName)); + + AMQQueue queue = _queueFactory.createQueue(UUIDGenerator.generateRandomUUID(), + queueName, + false, + "owner", + false, + false, + false, + null); + + assertEquals("Unexpected maximum delivery count", 5, queue.getMaximumDeliveryCount()); + Exchange altExchange = queue.getAlternateExchange(); + assertNotNull("Queue should have an alternate exchange as DLQ is enabled", altExchange); + assertEquals("Alternate exchange name was not as expected", dlExchangeName, altExchange.getName()); + assertEquals("Alternate exchange type was not as expected", ExchangeDefaults.FANOUT_EXCHANGE_CLASS, altExchange.getTypeName()); + + assertNotNull("The alternate exchange was not registered as expected", _virtualHost.getExchange(dlExchangeName)); + assertEquals("The registered exchange was not the expected exchange instance", altExchange, _virtualHost.getExchange(dlExchangeName)); + + AMQQueue dlQueue = _virtualHost.getQueue(dlQueueName); + assertNotNull("The DLQ was not registered as expected", dlQueue); + assertTrue("DLQ should have been bound to the alternate exchange", altExchange.isBound(dlQueue)); + assertNull("DLQ should have no alternate exchange", dlQueue.getAlternateExchange()); + assertEquals("DLQ should have a zero maximum delivery count", 0, dlQueue.getMaximumDeliveryCount()); + + //2 queues should have been registered + verifyRegisteredQueueCount(2); + } + + /** + * Tests that setting the {@link QueueArgumentsConverter#X_QPID_DLQ_ENABLED} argument false does not + * result in the alternate exchange being set and DLQ being created. + * @throws AMQException + */ + public void testDeadLetterQueueDisabled() throws AMQException + { + Map attributes = Collections.singletonMap(Queue.CREATE_DLQ_ON_CREATION, (Object) false); + + String queueName = "testDeadLetterQueueDisabled"; + String dlExchangeName = queueName + DefaultExchangeFactory.DEFAULT_DLE_NAME_SUFFIX; + String dlQueueName = queueName + AMQQueueFactory.DEFAULT_DLQ_NAME_SUFFIX; + + assertNull("The DLQ should not yet exist", _virtualHost.getQueue(dlQueueName)); + assertNull("The alternate exchange should not exist", _virtualHost.getExchange(dlExchangeName)); + + AMQQueue queue = _queueFactory.createQueue(UUIDGenerator.generateRandomUUID(), + queueName, + false, + "owner", + false, + false, + false, + attributes); + + assertNull("Queue should not have an alternate exchange as DLQ is disabled", queue.getAlternateExchange()); + assertNull("The alternate exchange should still not exist", _virtualHost.getExchange(dlExchangeName)); + + assertNull("The DLQ should still not exist", _virtualHost.getQueue(dlQueueName)); + + //only 1 queue should have been registered + verifyRegisteredQueueCount(1); + } + + /** + * Tests that setting the {@link QueueArgumentsConverter#X_QPID_DLQ_ENABLED} argument true but + * creating an auto-delete queue, does not result in the alternate exchange + * being set and DLQ being created. + * @throws AMQException + */ + public void testDeadLetterQueueNotCreatedForAutodeleteQueues() throws AMQException + { + Map attributes = Collections.singletonMap(Queue.CREATE_DLQ_ON_CREATION, (Object) true); + + String queueName = "testDeadLetterQueueNotCreatedForAutodeleteQueues"; + String dlExchangeName = queueName + DefaultExchangeFactory.DEFAULT_DLE_NAME_SUFFIX; + String dlQueueName = queueName + AMQQueueFactory.DEFAULT_DLQ_NAME_SUFFIX; + + assertNull("The DLQ should not yet exist", _virtualHost.getQueue(dlQueueName)); + assertNull("The alternate exchange should not exist", _virtualHost.getExchange(dlExchangeName)); + + //create an autodelete queue + AMQQueue queue = _queueFactory.createQueue(UUIDGenerator.generateRandomUUID(), + queueName, + false, + "owner", + true, + false, + false, + attributes); + assertTrue("Queue should be autodelete", queue.isAutoDelete()); + + //ensure that the autodelete property overrides the request to enable DLQ + assertNull("Queue should not have an alternate exchange as queue is autodelete", queue.getAlternateExchange()); + assertNull("The alternate exchange should not exist as queue is autodelete", _virtualHost.getExchange(dlExchangeName)); + assertNull("The DLQ should not exist as queue is autodelete", _virtualHost.getQueue(dlQueueName)); + + //only 1 queue should have been registered + verifyRegisteredQueueCount(1); + } + + /** + * Tests that setting the {@link QueueArgumentsConverter#X_QPID_MAXIMUM_DELIVERY_COUNT} argument has + * the desired effect. + */ + public void testMaximumDeliveryCount() throws Exception + { + Map attributes = Collections.singletonMap(Queue.MAXIMUM_DELIVERY_ATTEMPTS, (Object) 5); + + final AMQQueue queue = _queueFactory.createQueue(UUIDGenerator.generateRandomUUID(), + "testMaximumDeliveryCount", + false, + "owner", + false, + false, + false, + attributes); + + assertNotNull("The queue was not registered as expected ", queue); + assertEquals("Maximum delivery count not as expected", 5, queue.getMaximumDeliveryCount()); + + verifyRegisteredQueueCount(1); + } + + /** + * Tests that omitting the {@link QueueArgumentsConverter#X_QPID_MAXIMUM_DELIVERY_COUNT} argument means + * that queue is created with a default maximumDeliveryCount of zero (unless set in config). + */ + public void testMaximumDeliveryCountDefault() throws Exception + { + final AMQQueue queue = _queueFactory.createQueue(UUIDGenerator.generateRandomUUID(), + "testMaximumDeliveryCount", + false, + "owner", + false, + false, + false, + null); + + assertNotNull("The queue was not registered as expected ", queue); + assertEquals("Maximum delivery count not as expected", 0, queue.getMaximumDeliveryCount()); + + verifyRegisteredQueueCount(1); + } + + /** + * Tests queue creation with queue name set to null + */ + public void testQueueNameNullValidation() + { + try + { + _queueFactory.createQueue(UUIDGenerator.generateRandomUUID(), null, false, "owner", true, false, + false, + null); + fail("queue with null name can not be created!"); + } + catch (Exception e) + { + assertTrue(e instanceof IllegalArgumentException); + assertEquals("Queue name must not be null", e.getMessage()); + } + } + + /** + * Tests queue creation with queue name length less 255 characters but + * corresponding DLQ name length greater than 255. + */ + public void testQueueNameWithLengthLessThan255ButDLQNameWithLengthGreaterThan255() + { + String queueName = "test-" + generateStringWithLength('a', 245); + try + { + // change DLQ name to make its length bigger than exchange name + setTestSystemProperty(BrokerProperties.PROPERTY_DEAD_LETTER_EXCHANGE_SUFFIX, "_DLE"); + setTestSystemProperty(BrokerProperties.PROPERTY_DEAD_LETTER_QUEUE_SUFFIX, "_DLQUEUE"); + Map attributes = Collections.singletonMap(Queue.CREATE_DLQ_ON_CREATION, (Object) true); + _queueFactory.createQueue(UUIDGenerator.generateRandomUUID(), queueName, false, "owner", + false, false, false, attributes); + fail("queue with DLQ name having more than 255 characters can not be created!"); + } + catch (Exception e) + { + assertTrue("Unexpected exception is thrown!", e instanceof IllegalArgumentException); + assertTrue("Unexpected exception message!", e.getMessage().contains("DLQ queue name") + && e.getMessage().contains("length exceeds limit of 255")); + } + } + + /** + * Tests queue creation with queue name length less 255 characters but + * corresponding DL exchange name length greater than 255. + */ + public void testQueueNameWithLengthLessThan255ButDLExchangeNameWithLengthGreaterThan255() + { + String queueName = "test-" + generateStringWithLength('a', 245); + try + { + // change DLQ name to make its length bigger than exchange name + setTestSystemProperty(BrokerProperties.PROPERTY_DEAD_LETTER_EXCHANGE_SUFFIX, "_DLEXCHANGE"); + setTestSystemProperty(BrokerProperties.PROPERTY_DEAD_LETTER_QUEUE_SUFFIX, "_DLQ"); + Map attributes = Collections.singletonMap(Queue.CREATE_DLQ_ON_CREATION, (Object) true); + _queueFactory.createQueue(UUIDGenerator.generateRandomUUID(), queueName, false, "owner", + false, false, false, attributes); + fail("queue with DLE name having more than 255 characters can not be created!"); + } + catch (Exception e) + { + assertTrue("Unexpected exception is thrown!", e instanceof IllegalArgumentException); + assertTrue("Unexpected exception message!", e.getMessage().contains("DL exchange name") + && e.getMessage().contains("length exceeds limit of 255")); + } + } + + public void testMessageGroupFromConfig() throws Exception + { + + Map arguments = new HashMap(); + arguments.put("qpid.group_header_key","mykey"); + arguments.put("qpid.shared_msg_group","1"); + + QueueConfiguration qConf = mock(QueueConfiguration.class); + when(qConf.getArguments()).thenReturn(arguments); + when(qConf.getName()).thenReturn("test"); + + AMQQueue queue = _queueFactory.createAMQQueueImpl(qConf); + assertEquals("mykey", queue.getAttribute(Queue.MESSAGE_GROUP_KEY)); + assertEquals(Boolean.TRUE, queue.getAttribute(Queue.MESSAGE_GROUP_SHARED_GROUPS)); + } + + private String generateStringWithLength(char ch, int length) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) + { + sb.append(ch); + } + return sb.toString(); + } + + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/ConflationQueueListTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/ConflationQueueListTest.java new file mode 100644 index 0000000000..6538724a71 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/ConflationQueueListTest.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.queue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import junit.framework.TestCase; + +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ConflationQueueListTest extends TestCase +{ + private static final String CONFLATION_KEY = "CONFLATION_KEY"; + + private static final String TEST_KEY_VALUE = "testKeyValue"; + private static final String TEST_KEY_VALUE1 = "testKeyValue1"; + private static final String TEST_KEY_VALUE2 = "testKeyValue2"; + + private ConflationQueueList _list; + private AMQQueue _queue = createTestQueue(); + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _list = new ConflationQueueList(_queue, CONFLATION_KEY); + } + + public void testListHasNoEntries() + { + int numberOfEntries = countEntries(_list); + assertEquals(0, numberOfEntries); + } + + public void testAddMessageWithoutConflationKeyValue() + { + ServerMessage message = createTestServerMessage(null); + + _list.add(message); + int numberOfEntries = countEntries(_list); + assertEquals(1, numberOfEntries); + } + + public void testAddAndDiscardMessageWithoutConflationKeyValue() + { + ServerMessage message = createTestServerMessage(null); + + QueueEntry addedEntry = _list.add(message); + addedEntry.discard(); + + int numberOfEntries = countEntries(_list); + assertEquals(0, numberOfEntries); + } + + public void testAddMessageWithConflationKeyValue() + { + ServerMessage message = createTestServerMessage(TEST_KEY_VALUE); + + _list.add(message); + int numberOfEntries = countEntries(_list); + assertEquals(1, numberOfEntries); + } + + public void testAddAndRemoveMessageWithConflationKeyValue() + { + ServerMessage message = createTestServerMessage(TEST_KEY_VALUE); + + QueueEntry addedEntry = _list.add(message); + addedEntry.discard(); + + int numberOfEntries = countEntries(_list); + assertEquals(0, numberOfEntries); + } + + public void testAddTwoMessagesWithDifferentConflationKeyValue() + { + ServerMessage message1 = createTestServerMessage(TEST_KEY_VALUE1); + ServerMessage message2 = createTestServerMessage(TEST_KEY_VALUE2); + + _list.add(message1); + _list.add(message2); + + int numberOfEntries = countEntries(_list); + assertEquals(2, numberOfEntries); + } + + public void testAddTwoMessagesWithSameConflationKeyValue() + { + ServerMessage message1 = createTestServerMessage(TEST_KEY_VALUE); + ServerMessage message2 = createTestServerMessage(TEST_KEY_VALUE); + + _list.add(message1); + _list.add(message2); + + int numberOfEntries = countEntries(_list); + assertEquals(1, numberOfEntries); + } + + public void testSupersededEntryIsDiscardedOnRelease() + { + ServerMessage message1 = createTestServerMessage(TEST_KEY_VALUE); + ServerMessage message2 = createTestServerMessage(TEST_KEY_VALUE); + + QueueEntry entry1 = _list.add(message1); + entry1.acquire(); // simulate an in-progress delivery to consumer + + _list.add(message2); + assertFalse(entry1.isDeleted()); + + assertEquals(2, countEntries(_list)); + + entry1.release(); // simulate consumer rollback/recover + + assertEquals(1, countEntries(_list)); + assertTrue(entry1.isDeleted()); + } + + public void testConflationMapMaintained() + { + assertEquals(0, _list.getLatestValuesMap().size()); + + ServerMessage message = createTestServerMessage(TEST_KEY_VALUE); + + QueueEntry addedEntry = _list.add(message); + + assertEquals(1, countEntries(_list)); + assertEquals(1, _list.getLatestValuesMap().size()); + + addedEntry.discard(); + + assertEquals(0, countEntries(_list)); + assertEquals(0, _list.getLatestValuesMap().size()); + } + + public void testConflationMapMaintainedWithDifferentConflationKeyValue() + { + + assertEquals(0, _list.getLatestValuesMap().size()); + + ServerMessage message1 = createTestServerMessage(TEST_KEY_VALUE1); + ServerMessage message2 = createTestServerMessage(TEST_KEY_VALUE2); + + QueueEntry addedEntry1 = _list.add(message1); + QueueEntry addedEntry2 = _list.add(message2); + + assertEquals(2, countEntries(_list)); + assertEquals(2, _list.getLatestValuesMap().size()); + + addedEntry1.discard(); + addedEntry2.discard(); + + assertEquals(0, countEntries(_list)); + assertEquals(0, _list.getLatestValuesMap().size()); + } + + private int countEntries(ConflationQueueList list) + { + QueueEntryIterator iterator = list.iterator(); + int count = 0; + while(iterator.advance()) + { + count++; + } + return count; + } + + private ServerMessage createTestServerMessage(String conflationKeyValue) + { + ServerMessage mockMessage = mock(ServerMessage.class); + + AMQMessageHeader messageHeader = mock(AMQMessageHeader.class); + when(messageHeader.getHeader(CONFLATION_KEY)).thenReturn(conflationKeyValue); + when(mockMessage.getMessageHeader()).thenReturn(messageHeader); + + MessageReference messageReference = mock(MessageReference.class); + when(mockMessage.newReference()).thenReturn(messageReference); + when(messageReference.getMessage()).thenReturn(mockMessage); + + return mockMessage; + } + + private AMQQueue createTestQueue() + { + AMQQueue queue = mock(AMQQueue.class); + VirtualHost virtualHost = mock(VirtualHost.class); + when(queue.getVirtualHost()).thenReturn(virtualHost); + + return queue; + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/InboundMessageAdapterTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/InboundMessageAdapterTest.java new file mode 100644 index 0000000000..584e26d88f --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/InboundMessageAdapterTest.java @@ -0,0 +1,89 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.queue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.test.utils.QpidTestCase; + +public class InboundMessageAdapterTest extends QpidTestCase +{ + private ServerMessage _mockMessage; + private QueueEntry _mockQueueEntry; + private InboundMessageAdapter _inboundMessageAdapter; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _mockMessage = mock(ServerMessage.class); + _mockQueueEntry = mock(QueueEntry.class); + when(_mockQueueEntry.getMessage()).thenReturn(_mockMessage); + + _inboundMessageAdapter = new InboundMessageAdapter(_mockQueueEntry); + } + + public void testGetRoutingKey() throws Exception + { + String routingKey = getTestName(); + when(_mockMessage.getRoutingKey()).thenReturn(routingKey); + + assertEquals("Unexpected value for routing key", routingKey, _inboundMessageAdapter.getRoutingKey()); + } + + + public void testGetMessageHeader() throws Exception + { + AMQMessageHeader mockMessageHeader = mock(AMQMessageHeader.class); + when(_mockQueueEntry.getMessageHeader()).thenReturn(mockMessageHeader); + + assertSame("unexpected message header", mockMessageHeader, _inboundMessageAdapter.getMessageHeader()); + } + + public void testIsRedelivered() throws Exception + { + when(_mockQueueEntry.isRedelivered()).thenReturn(true); + assertTrue("unexpected isRedelivered value", _inboundMessageAdapter.isRedelivered()); + + when(_mockQueueEntry.isRedelivered()).thenReturn(false); + assertFalse("unexpected isRedelivered value", _inboundMessageAdapter.isRedelivered()); + } + + public void testIsPersistent() throws Exception + { + when(_mockQueueEntry.isPersistent()).thenReturn(true); + assertTrue("unexpected isPersistent value", _inboundMessageAdapter.isPersistent()); + + when(_mockQueueEntry.isPersistent()).thenReturn(false); + assertFalse("unexpected isPersistent value", _inboundMessageAdapter.isPersistent()); + } + + public void testGetSize() throws Exception + { + long size = 32526215; + when(_mockQueueEntry.getSize()).thenReturn(size); + assertEquals("unexpected getSize value", size, _inboundMessageAdapter.getSize()); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java new file mode 100644 index 0000000000..2a0c12ff3e --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/MockAMQQueue.java @@ -0,0 +1,615 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.binding.Binding; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.security.AuthorizationHolder; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; + +public class MockAMQQueue implements AMQQueue +{ + private boolean _deleted = false; + private String _name; + private VirtualHost _virtualhost; + + private AuthorizationHolder _authorizationHolder; + + private AMQSessionModel _exclusiveOwner; + private List _bindings = new CopyOnWriteArrayList(); + private boolean _autoDelete; + + public MockAMQQueue(String name) + { + _name = name; + } + + public boolean getDeleteOnNoConsumers() + { + return false; + } + + public void setDeleteOnNoConsumers(boolean b) + { + } + + public void addBinding(final Binding binding) + { + _bindings.add(binding); + } + + public void removeBinding(final Binding binding) + { + _bindings.remove(binding); + } + + public List getBindings() + { + return _bindings; + } + + public int getBindingCount() + { + return 0; + } + + public LogSubject getLogSubject() + { + return new LogSubject() + { + public String toLogString() + { + return "[MockAMQQueue]"; + } + + }; + } + + public long getUnackedMessageBytes() + { + return 0; + } + + public long getMessageDequeueCount() + { + return 0; + } + + public long getTotalEnqueueSize() + { + return 0; + } + + public long getTotalDequeueSize() + { + return 0; + } + + public long getTotalDequeueCount() + { + return 0; + } + + public long getTotalEnqueueCount() + { + return 0; + } + + public int getBindingCountHigh() + { + return 0; + } + + public long getPersistentByteEnqueues() + { + return 0; + } + + public long getPersistentByteDequeues() + { + return 0; + } + + public long getPersistentMsgEnqueues() + { + return 0; + } + + public long getPersistentMsgDequeues() + { + return 0; + } + + public void purge(final long request) + { + + } + + public long getCreateTime() + { + return 0; + } + + public void setNoLocal(boolean b) + { + + } + + public UUID getId() + { + return null; + } + + public boolean isDurable() + { + return false; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public void setAutoDelete(boolean autodelete) + { + _autoDelete = autodelete; + } + + + public String getOwner() + { + return null; + } + + public void setVirtualHost(VirtualHost virtualhost) + { + _virtualhost = virtualhost; + } + + public VirtualHost getVirtualHost() + { + return _virtualhost; + } + + public String getName() + { + return _name; + } + + public void registerSubscription(Subscription subscription, boolean exclusive) throws AMQException + { + + } + + public void unregisterSubscription(Subscription subscription) throws AMQException + { + + } + + public Collection getConsumers() + { + return Collections.emptyList(); + } + + public void addSubscriptionRegistrationListener(final SubscriptionRegistrationListener listener) + { + + } + + public void removeSubscriptionRegistrationListener(final SubscriptionRegistrationListener listener) + { + + } + + public int getConsumerCount() + { + return 0; + } + + public int getActiveConsumerCount() + { + return 0; + } + + public boolean hasExclusiveSubscriber() + { + return false; + } + + public boolean isUnused() + { + return false; + } + + public boolean isEmpty() + { + return false; + } + + public int getMessageCount() + { + return 0; + } + + public int getUndeliveredMessageCount() + { + return 0; + } + + public long getQueueDepth() + { + return 0; + } + + public long getReceivedMessageCount() + { + return 0; + } + + public long getOldestMessageArrivalTime() + { + return 0; + } + + public boolean isDeleted() + { + return _deleted; + } + + public int delete() throws AMQException + { + _deleted = true; + return getMessageCount(); + } + + public void enqueue(ServerMessage message) throws AMQException + { + } + + public void enqueue(ServerMessage message, PostEnqueueAction action) throws AMQException + { + } + + + public void enqueue(ServerMessage message, boolean sync, PostEnqueueAction action) throws AMQException + { + } + + public void requeue(QueueEntry entry) + { + } + + public void requeue(QueueEntryImpl storeContext, Subscription subscription) + { + } + + public void dequeue(QueueEntry entry, Subscription sub) + { + } + + public boolean resend(QueueEntry entry, Subscription subscription) throws AMQException + { + return false; + } + + public void addQueueDeleteTask(Task task) + { + } + + public void removeQueueDeleteTask(final Task task) + { + } + + public List getMessagesOnTheQueue() + { + return null; + } + + public List getMessagesOnTheQueue(long fromMessageId, long toMessageId) + { + return null; + } + + public List getMessagesOnTheQueue(int num) + { + return null; + } + + public List getMessagesOnTheQueue(int num, int offest) + { + return null; + } + + public QueueEntry getMessageOnTheQueue(long messageId) + { + return null; + } + + public List getMessagesRangeOnTheQueue(long fromPosition, long toPosition) + { + return null; + } + + public long getMaximumMessageSize() + { + return 0; + } + + public void setMaximumMessageSize(long value) + { + + } + + public long getMaximumMessageCount() + { + return 0; + } + + public void setMaximumMessageCount(long value) + { + + } + + public long getMaximumQueueDepth() + { + return 0; + } + + public void setMaximumQueueDepth(long value) + { + + } + + public long getMaximumMessageAge() + { + return 0; + } + + public void setMaximumMessageAge(long maximumMessageAge) + { + + } + + public long getMinimumAlertRepeatGap() + { + return 0; + } + + public void deleteMessageFromTop() + { + + } + + public long clearQueue() + { + return 0; + } + + + public void checkMessageStatus() throws AMQException + { + + } + + public Set getNotificationChecks() + { + return null; + } + + public void flushSubscription(Subscription sub) throws AMQException + { + + } + + public void deliverAsync(Subscription sub) + { + + } + + public void deliverAsync() + { + + } + + public void stop() + { + + } + + public boolean isExclusive() + { + return false; + } + + public Exchange getAlternateExchange() + { + return null; + } + + public void setAlternateExchange(Exchange exchange) + { + + } + + @Override + public Collection getAvailableAttributes() + { + return null; + } + + @Override + public Object getAttribute(String attrName) + { + return null; + } + + public void checkCapacity(AMQSessionModel channel) + { + } + + public int compareTo(AMQQueue o) + { + return 0; + } + + public void setMinimumAlertRepeatGap(long value) + { + + } + + public long getCapacity() + { + return 0; + } + + public void setCapacity(long capacity) + { + + } + + public long getFlowResumeCapacity() + { + return 0; + } + + public void setFlowResumeCapacity(long flowResumeCapacity) + { + + } + + public void configure(QueueConfiguration config) + { + + } + + public AuthorizationHolder getAuthorizationHolder() + { + return _authorizationHolder; + } + + public void setAuthorizationHolder(final AuthorizationHolder authorizationHolder) + { + _authorizationHolder = authorizationHolder; + } + + public AMQSessionModel getExclusiveOwningSession() + { + return _exclusiveOwner; + } + + public void setExclusiveOwningSession(AMQSessionModel exclusiveOwner) + { + _exclusiveOwner = exclusiveOwner; + } + + public boolean isOverfull() + { + return false; + } + + public int getConsumerCountHigh() + { + return 0; + } + + public long getByteTxnEnqueues() + { + return 0; + } + + public long getMsgTxnEnqueues() + { + return 0; + } + + public long getByteTxnDequeues() + { + return 0; + } + + public long getMsgTxnDequeues() + { + return 0; + } + + public void decrementUnackedMsgCount(QueueEntry queueEntry) + { + + } + + public long getUnackedMessageCount() + { + return 0; + } + + public long getUnackedMessageCountHigh() + { + return 0; + } + + public void setExclusive(boolean exclusive) + { + } + + public int getMaximumDeliveryCount() + { + return 0; + } + + public void setMaximumDeliveryCount(int maximumDeliveryCount) + { + } + + public void visit(final QueueEntryVisitor visitor) + { + } + + @Override + public void setNotificationListener(NotificationListener listener) + { + } + + @Override + public void setDescription(String description) + { + } + + @Override + public String getDescription() + { + return null; + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java new file mode 100644 index 0000000000..f5d4f1219d --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/MockQueueEntry.java @@ -0,0 +1,253 @@ +/* +* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.message.AMQMessageHeader; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.subscription.Subscription; + +public class MockQueueEntry implements QueueEntry +{ + + private ServerMessage _message; + + public boolean acquire() + { + return false; + } + + public boolean acquire(Subscription sub) + { + return false; + } + + public boolean acquiredBySubscription() + { + return false; + } + + public boolean isAcquiredBy(Subscription subscription) + { + return false; + } + + public void addStateChangeListener(StateChangeListener listener) + { + + } + + public boolean delete() + { + return false; + } + + public void dequeue() + { + + } + + public void discard() + { + + } + + public void routeToAlternate() + { + + } + + public void dispose() + { + + } + + public boolean expired() throws AMQException + { + return false; + } + + public boolean isAvailable() + { + return false; + } + + public Subscription getDeliveredSubscription() + { + return null; + } + + public boolean getDeliveredToConsumer() + { + return false; + } + + public ServerMessage getMessage() + { + return _message; + } + + public AMQQueue getQueue() + { + return null; + } + + public long getSize() + { + return 0; + } + + public boolean isAcquired() + { + return false; + } + + public boolean isDeleted() + { + return false; + } + + + public boolean isQueueDeleted() + { + + return false; + } + + + public boolean isRejectedBy(long subscriptionId) + { + + return false; + } + + + public void reject() + { + + + } + + + public void release() + { + + + } + + + public boolean removeStateChangeListener(StateChangeListener listener) + { + + return false; + } + + + public void requeue() + { + + + } + + public void requeue(Subscription subscription) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + + public void setDeliveredToSubscription() + { + + + } + + + public void setRedelivered() + { + + + } + + public AMQMessageHeader getMessageHeader() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isPersistent() + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isRedelivered() + { + return false; + } + + + public int compareTo(QueueEntry o) + { + + return 0; + } + + public void setMessage(ServerMessage msg) + { + _message = msg; + } + + public boolean isDequeued() + { + return false; + } + + public boolean isDispensed() + { + return false; + } + + public QueueEntry getNextNode() + { + return null; + } + + public QueueEntry getNextValidEntry() + { + return null; + } + + @Override + public int getDeliveryCount() + { + return 0; + } + + @Override + public void incrementDeliveryCount() + { + } + + @Override + public void decrementDeliveryCount() + { + } + + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/NotificationCheckTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/NotificationCheckTest.java new file mode 100644 index 0000000000..df2de7f0e0 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/NotificationCheckTest.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.queue; + +import static org.mockito.Matchers.contains; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import static org.apache.qpid.server.queue.NotificationCheck.MESSAGE_AGE_ALERT; +import static org.apache.qpid.server.queue.NotificationCheck.MESSAGE_COUNT_ALERT; +import static org.apache.qpid.server.queue.NotificationCheck.MESSAGE_SIZE_ALERT; +import static org.apache.qpid.server.queue.NotificationCheck.QUEUE_DEPTH_ALERT; + + +import junit.framework.TestCase; + +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.AMQQueue.NotificationListener; + +public class NotificationCheckTest extends TestCase +{ + + private ServerMessage _message = mock(ServerMessage.class); + private AMQQueue _queue = mock(AMQQueue.class); + private NotificationListener _listener = mock(NotificationListener.class); + + public void testMessageCountAlertFires() throws Exception + { + when(_queue.getMaximumMessageCount()).thenReturn(1000l); + when(_queue.getMessageCount()).thenReturn(999, 1000, 1001); + + MESSAGE_COUNT_ALERT.notifyIfNecessary(_message, _queue, _listener); + verifyZeroInteractions(_listener); + + MESSAGE_COUNT_ALERT.notifyIfNecessary(_message, _queue, _listener); + verify(_listener).notifyClients(eq(MESSAGE_COUNT_ALERT), eq(_queue), eq("1000: Maximum count on queue threshold (1000) breached.")); + + MESSAGE_COUNT_ALERT.notifyIfNecessary(_message, _queue, _listener); + verify(_listener).notifyClients(eq(MESSAGE_COUNT_ALERT), eq(_queue), eq("1001: Maximum count on queue threshold (1000) breached.")); + } + + public void testMessageSizeAlertFires() throws Exception + { + when(_queue.getMaximumMessageSize()).thenReturn(1024l); + when(_message.getSize()).thenReturn(1023l, 1024l, 1025l); + + MESSAGE_SIZE_ALERT.notifyIfNecessary(_message, _queue, _listener); + verifyZeroInteractions(_listener); + + MESSAGE_SIZE_ALERT.notifyIfNecessary(_message, _queue, _listener); + verify(_listener).notifyClients(eq(MESSAGE_SIZE_ALERT), eq(_queue), contains("1024b : Maximum message size threshold (1024) breached.")); + + MESSAGE_SIZE_ALERT.notifyIfNecessary(_message, _queue, _listener); + verify(_listener).notifyClients(eq(MESSAGE_SIZE_ALERT), eq(_queue), contains("1025b : Maximum message size threshold (1024) breached.")); + } + + public void testMessageAgeAlertFires() throws Exception + { + long now = System.currentTimeMillis(); + when(_queue.getMaximumMessageAge()).thenReturn(1000l); + when(_queue.getOldestMessageArrivalTime()).thenReturn(now, now - 15000); + + MESSAGE_AGE_ALERT.notifyIfNecessary(_message, _queue, _listener); + verifyZeroInteractions(_listener); + + MESSAGE_AGE_ALERT.notifyIfNecessary(_message, _queue, _listener); + // Uses contains as first part of message is nondeterministic + verify(_listener).notifyClients(eq(MESSAGE_AGE_ALERT), eq(_queue), contains("s : Maximum age on queue threshold (1s) breached.")); + } + + public void testQueueDepthAlertFires() throws Exception + { + when(_queue.getMaximumQueueDepth()).thenReturn(1024l); + when(_queue.getQueueDepth()).thenReturn(1023l, 1024l, 2048l); + + QUEUE_DEPTH_ALERT.notifyIfNecessary(_message, _queue, _listener); + verifyZeroInteractions(_listener); + + QUEUE_DEPTH_ALERT.notifyIfNecessary(_message, _queue, _listener); + verify(_listener).notifyClients(eq(QUEUE_DEPTH_ALERT), eq(_queue), eq("1Kb : Maximum queue depth threshold (1Kb) breached.")); + + QUEUE_DEPTH_ALERT.notifyIfNecessary(_message, _queue, _listener); + verify(_listener).notifyClients(eq(QUEUE_DEPTH_ALERT), eq(_queue), eq("2Kb : Maximum queue depth threshold (1Kb) breached.")); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/PriorityQueueListTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/PriorityQueueListTest.java new file mode 100644 index 0000000000..e8c0470915 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/PriorityQueueListTest.java @@ -0,0 +1,117 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.queue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.test.utils.QpidTestCase; + +public class PriorityQueueListTest extends QpidTestCase +{ + private static final byte[] PRIORITIES = {4, 5, 5, 4}; + PriorityQueueList _list = new PriorityQueueList(null, 10); + + private QueueEntry _priority4message1; + private QueueEntry _priority4message2; + private QueueEntry _priority5message1; + private QueueEntry _priority5message2; + + protected void setUp() + { + QueueEntry[] entries = new QueueEntry[PRIORITIES.length]; + + for (int i = 0; i < PRIORITIES.length; i++) + { + ServerMessage message = mock(ServerMessage.class); + AMQMessageHeader header = mock(AMQMessageHeader.class); + @SuppressWarnings({ "rawtypes", "unchecked" }) + MessageReference ref = mock(MessageReference.class); + + when(message.getMessageHeader()).thenReturn(header); + when(message.newReference()).thenReturn(ref); + when(ref.getMessage()).thenReturn(message); + when(header.getPriority()).thenReturn(PRIORITIES[i]); + + entries[i] = _list.add(message); + } + + _priority4message1 = entries[0]; + _priority4message2 = entries[3]; + _priority5message1 = entries[1]; + _priority5message2 = entries[2]; + } + + public void testPriorityQueueEntryCompareToItself() + { + //check messages compare to themselves properly + assertEquals("message should compare 'equal' to itself", + 0, _priority4message1.compareTo(_priority4message1)); + + assertEquals("message should compare 'equal' to itself", + 0, _priority5message2.compareTo(_priority5message2)); + } + + public void testPriorityQueueEntryCompareToSamePriority() + { + //check messages with the same priority are ordered properly + assertEquals("first message should be 'earlier' than second message of the same priority", + -1, _priority4message1.compareTo(_priority4message2)); + + assertEquals("first message should be 'earlier' than second message of the same priority", + -1, _priority5message1.compareTo(_priority5message2)); + + //and in reverse + assertEquals("second message should be 'later' than first message of the same priority", + 1, _priority4message2.compareTo(_priority4message1)); + + assertEquals("second message should be 'later' than first message of the same priority", + 1, _priority5message2.compareTo(_priority5message1)); + } + + public void testPriorityQueueEntryCompareToDifferentPriority() + { + //check messages with higher priority are ordered 'earlier' than those with lower priority + assertEquals("first message with priority 5 should be 'earlier' than first message of priority 4", + -1, _priority5message1.compareTo(_priority4message1)); + assertEquals("first message with priority 5 should be 'earlier' than second message of priority 4", + -1, _priority5message1.compareTo(_priority4message2)); + + assertEquals("second message with priority 5 should be 'earlier' than first message of priority 4", + -1, _priority5message2.compareTo(_priority4message1)); + assertEquals("second message with priority 5 should be 'earlier' than second message of priority 4", + -1, _priority5message2.compareTo(_priority4message2)); + + //and in reverse + assertEquals("first message with priority 4 should be 'later' than first message of priority 5", + 1, _priority4message1.compareTo(_priority5message1)); + assertEquals("first message with priority 4 should be 'later' than second message of priority 5", + 1, _priority4message1.compareTo(_priority5message2)); + + assertEquals("second message with priority 4 should be 'later' than first message of priority 5", + 1, _priority4message2.compareTo(_priority5message1)); + assertEquals("second message with priority 4 should be 'later' than second message of priority 5", + 1, _priority4message2.compareTo(_priority5message2)); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/QueueEntryImplTestBase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/QueueEntryImplTestBase.java new file mode 100644 index 0000000000..d348c3e67b --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/QueueEntryImplTestBase.java @@ -0,0 +1,253 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.message.ServerMessage; +import org.apache.qpid.server.queue.QueueEntry.EntryState; +import org.apache.qpid.server.subscription.MockSubscription; +import org.apache.qpid.server.subscription.Subscription; + +import java.lang.reflect.Field; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link QueueEntryImpl} + */ +public abstract class QueueEntryImplTestBase extends TestCase +{ + // tested entry + protected QueueEntryImpl _queueEntry; + protected QueueEntryImpl _queueEntry2; + protected QueueEntryImpl _queueEntry3; + + public abstract QueueEntryImpl getQueueEntryImpl(int msgid) throws AMQException; + + public abstract void testCompareTo(); + + public abstract void testTraverseWithNoDeletedEntries(); + + public abstract void testTraverseWithDeletedEntries(); + + public void setUp() throws Exception + { + _queueEntry = getQueueEntryImpl(1); + _queueEntry2 = getQueueEntryImpl(2); + _queueEntry3 = getQueueEntryImpl(3); + } + + public void testAquire() + { + assertTrue("Queue entry should be in AVAILABLE state before invoking of acquire method", + _queueEntry.isAvailable()); + acquire(); + } + + public void testDequeue() + { + dequeue(); + } + + public void testDelete() + { + delete(); + } + + /** + * Tests release method for entry in acquired state. + *

+ * Entry in state ACQUIRED should be released and its status should be + * changed to AVAILABLE. + */ + public void testReleaseAquired() + { + acquire(); + _queueEntry.release(); + assertTrue("Queue entry should be in AVAILABLE state after invoking of release method", + _queueEntry.isAvailable()); + } + + /** + * Tests release method for entry in dequeued state. + *

+ * Invoking release on dequeued entry should not have any effect on its + * state. + */ + public void testReleaseDequeued() + { + dequeue(); + _queueEntry.release(); + EntryState state = getState(); + assertEquals("Invoking of release on entry in DEQUEUED state should not have any effect", + QueueEntry.DEQUEUED_STATE, state); + } + + /** + * Tests release method for entry in deleted state. + *

+ * Invoking release on deleted entry should not have any effect on its + * state. + */ + public void testReleaseDeleted() + { + delete(); + _queueEntry.release(); + assertTrue("Invoking of release on entry in DELETED state should not have any effect", + _queueEntry.isDeleted()); + } + + /** + * A helper method to put tested object into deleted state and assert the state + */ + private void delete() + { + _queueEntry.delete(); + assertTrue("Queue entry should be in DELETED state after invoking of delete method", + _queueEntry.isDeleted()); + } + + /** + * A helper method to put tested entry into dequeue state and assert the sate + */ + private void dequeue() + { + acquire(); + _queueEntry.dequeue(); + EntryState state = getState(); + assertEquals("Queue entry should be in DEQUEUED state after invoking of dequeue method", + QueueEntry.DEQUEUED_STATE, state); + } + + /** + * A helper method to put tested entry into acquired state and assert the sate + */ + private void acquire() + { + _queueEntry.acquire(new MockSubscription()); + assertTrue("Queue entry should be in ACQUIRED state after invoking of acquire method", + _queueEntry.isAcquired()); + } + + /** + * A helper method to get entry state + * + * @return entry state + */ + private EntryState getState() + { + EntryState state = null; + try + { + Field f = QueueEntryImpl.class.getDeclaredField("_state"); + f.setAccessible(true); + state = (EntryState) f.get(_queueEntry); + } + catch (Exception e) + { + fail("Failure to get a state field: " + e.getMessage()); + } + return state; + } + + /** + * Tests rejecting a queue entry records the Subscription ID + * for later verification by isRejectedBy(subscriptionId). + */ + public void testRejectAndRejectedBy() + { + Subscription sub = new MockSubscription(); + long subId = sub.getSubscriptionID(); + + assertFalse("Queue entry should not yet have been rejected by the subscription", _queueEntry.isRejectedBy(subId)); + assertFalse("Queue entry should not yet have been acquired by a subscription", _queueEntry.isAcquired()); + + //acquire, reject, and release the message using the subscription + assertTrue("Queue entry should have been able to be acquired", _queueEntry.acquire(sub)); + _queueEntry.reject(); + _queueEntry.release(); + + //verify the rejection is recorded + assertTrue("Queue entry should have been rejected by the subscription", _queueEntry.isRejectedBy(subId)); + + //repeat rejection using a second subscription + Subscription sub2 = new MockSubscription(); + long sub2Id = sub2.getSubscriptionID(); + + assertFalse("Queue entry should not yet have been rejected by the subscription", _queueEntry.isRejectedBy(sub2Id)); + assertTrue("Queue entry should have been able to be acquired", _queueEntry.acquire(sub2)); + _queueEntry.reject(); + + //verify it still records being rejected by both subscriptions + assertTrue("Queue entry should have been rejected by the subscription", _queueEntry.isRejectedBy(subId)); + assertTrue("Queue entry should have been rejected by the subscription", _queueEntry.isRejectedBy(sub2Id)); + } + + /** + * Tests if entries in DEQUQUED or DELETED state are not returned by getNext method. + */ + public void testGetNext() + { + int numberOfEntries = 5; + QueueEntryImpl[] entries = new QueueEntryImpl[numberOfEntries]; + SimpleQueueEntryList queueEntryList = new SimpleQueueEntryList(new MockAMQQueue("test")); + + // create test entries + for(int i = 0; i < numberOfEntries ; i++) + { + ServerMessage message = mock(ServerMessage.class); + when(message.getMessageNumber()).thenReturn((long)i); + QueueEntryImpl entry = queueEntryList.add(message); + entries[i] = entry; + } + + // test getNext for not acquired entries + for(int i = 0; i < numberOfEntries ; i++) + { + QueueEntryImpl queueEntry = entries[i]; + QueueEntry next = queueEntry.getNextValidEntry(); + if (i < numberOfEntries - 1) + { + assertEquals("Unexpected entry from QueueEntryImpl#getNext()", entries[i + 1], next); + } + else + { + assertNull("The next entry after the last should be null", next); + } + } + + // delete second + entries[1].acquire(); + entries[1].delete(); + + // dequeue third + entries[2].acquire(); + entries[2].dequeue(); + + QueueEntry next = entries[0].getNextValidEntry(); + assertEquals("expected forth entry",entries[3], next); + next = next.getNextValidEntry(); + assertEquals("expected fifth entry", entries[4], next); + next = next.getNextValidEntry(); + assertNull("The next entry after the last should be null", next); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/QueueEntryListTestBase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/QueueEntryListTestBase.java new file mode 100644 index 0000000000..beb5bda7ff --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/QueueEntryListTestBase.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.queue; + +import junit.framework.TestCase; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Abstract test class for QueueEntryList implementations. + */ +public abstract class QueueEntryListTestBase extends TestCase +{ + protected static final AMQQueue _testQueue = new MockAMQQueue("test"); + public abstract QueueEntryList getTestList(); + public abstract QueueEntryList getTestList(boolean newList); + public abstract long getExpectedFirstMsgId(); + public abstract int getExpectedListLength(); + public abstract ServerMessage getTestMessageToAdd() throws AMQException; + + public void testGetQueue() + { + assertEquals("Unexpected head entry returned by getHead()", getTestList().getQueue(), _testQueue); + } + + /** + * Test to add a message with properties specific to the queue type. + * @see QueueEntryListTestBase#getTestList() + * @see QueueEntryListTestBase#getTestMessageToAdd() + * @throws AMQException + */ + public void testAddSpecificMessage() throws AMQException + { + final QueueEntryList list = getTestList(); + list.add(getTestMessageToAdd()); + + final QueueEntryIterator iter = list.iterator(); + int count = 0; + while(iter.advance()) + { + iter.getNode(); + count++; + } + assertEquals("List did not grow by one entry after an add", getExpectedListLength() + 1, count); + } + + /** + * Test to add a generic mock message. + * @see QueueEntryListTestBase#getTestList() + * @see QueueEntryListTestBase#getExpectedListLength() + * @throws AMQException + */ + public void testAddGenericMessage() throws AMQException + { + final QueueEntryList list = getTestList(); + final ServerMessage message = createServerMessage(666l); + list.add(message); + + final QueueEntryIterator iter = list.iterator(); + int count = 0; + while(iter.advance()) + { + iter.getNode(); + count++; + } + assertEquals("List did not grow by one entry after a generic message added", getExpectedListLength() + 1, count); + + } + + private ServerMessage createServerMessage(long number) + { + final ServerMessage message = mock(ServerMessage.class); + when(message.getMessageNumber()).thenReturn(number); + MessageReference ref = mock(MessageReference.class); + AMQMessageHeader hdr = mock(AMQMessageHeader.class); + when(ref.getMessage()).thenReturn(message); + when(message.newReference()).thenReturn(ref); + when(message.getMessageHeader()).thenReturn(hdr); + return message; + } + + /** + * Test for getting the next element in a queue list. + * @see QueueEntryListTestBase#getTestList() + * @see QueueEntryListTestBase#getExpectedListLength() + */ + public void testListNext() + { + final QueueEntryList entryList = getTestList(); + QueueEntry entry = entryList.getHead(); + int count = 0; + while(entryList.next(entry) != null) + { + entry = entryList.next(entry); + count++; + } + assertEquals("Get next didnt get all the list entries", getExpectedListLength(), count); + } + + /** + * Basic test for the associated QueueEntryIterator implementation. + * @see QueueEntryListTestBase#getTestList() + * @see QueueEntryListTestBase#getExpectedListLength() + */ + public void testIterator() + { + final QueueEntryIterator iter = getTestList().iterator(); + int count = 0; + while(iter.advance()) + { + iter.getNode(); + count++; + } + assertEquals("Iterator invalid", getExpectedListLength(), count); + } + + /** + * Test for associated QueueEntryIterator implementation that checks it handles "removed" messages. + * @see QueueEntryListTestBase#getTestList() + * @see QueueEntryListTestBase#getExpectedListLength() + */ + public void testDequedMessagedNotPresentInIterator() throws Exception + { + final int numberOfMessages = getExpectedListLength(); + final QueueEntryList entryList = getTestList(); + + // dequeue all even messages + final QueueEntryIterator it1 = entryList.iterator(); + int counter = 0; + while (it1.advance()) + { + final QueueEntry queueEntry = it1.getNode(); + if(counter++ % 2 == 0) + { + queueEntry.acquire(); + queueEntry.dequeue(); + } + } + + // iterate and check that dequeued messages are not returned by iterator + final QueueEntryIterator it2 = entryList.iterator(); + int counter2 = 0; + while(it2.advance()) + { + it2.getNode(); + counter2++; + } + final int expectedNumber = numberOfMessages / 2; + assertEquals("Expected " + expectedNumber + " number of entries in iterator but got " + counter2, + expectedNumber, counter2); + } + + /** + * Test to verify the head of the queue list is returned as expected. + * @see QueueEntryListTestBase#getTestList() + * @see QueueEntryListTestBase#getExpectedFirstMsgId() + */ + public void testGetHead() + { + final QueueEntry head = getTestList().getHead(); + assertNull("Head entry should not contain an actual message", head.getMessage()); + assertEquals("Unexpected message id for first list entry", getExpectedFirstMsgId(), getTestList().next(head) + .getMessage().getMessageNumber()); + } + + /** + * Test to verify the entry deletion handled correctly. + * @see QueueEntryListTestBase#getTestList() + */ + public void testEntryDeleted() + { + final QueueEntry head = getTestList().getHead(); + + final QueueEntry first = getTestList().next(head); + first.delete(); + + final QueueEntry second = getTestList().next(head); + assertNotSame("After deletion the next entry should be different", first.getMessage().getMessageNumber(), second + .getMessage().getMessageNumber()); + + final QueueEntry third = getTestList().next(first); + assertEquals("After deletion the deleted nodes next node should be the same as the next from head", second + .getMessage().getMessageNumber(), third.getMessage().getMessageNumber()); + } + + /** + * Tests that after the last node of the list is marked deleted but has not yet been removed, + * the iterator still ignores it and returns that it is 'atTail()' and can't 'advance()' + * + * @see QueueEntryListTestBase#getTestList() + * @see QueueEntryListTestBase#getExpectedListLength() + */ + public void testIteratorIgnoresDeletedFinalNode() throws Exception + { + QueueEntryList list = getTestList(true); + int i = 0; + + QueueEntry queueEntry1 = list.add(createServerMessage(i++)); + QueueEntry queueEntry2 = list.add(createServerMessage(i++)); + + assertSame(queueEntry2, list.next(queueEntry1)); + assertNull(list.next(queueEntry2)); + + //'delete' the 2nd QueueEntry + assertTrue("Deleting node should have succeeded", queueEntry2.delete()); + + QueueEntryIterator iter = list.iterator(); + + //verify the iterator isn't 'atTail', can advance, and returns the 1st QueueEntry + assertFalse("Iterator should not have been 'atTail'", iter.atTail()); + assertTrue("Iterator should have been able to advance", iter.advance()); + assertSame("Iterator returned unexpected QueueEntry", queueEntry1, iter.getNode()); + + //verify the iterator is atTail() and can't advance + assertTrue("Iterator should have been 'atTail'", iter.atTail()); + assertFalse("Iterator should not have been able to advance", iter.advance()); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SelfValidatingSortedQueueEntryList.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SelfValidatingSortedQueueEntryList.java new file mode 100644 index 0000000000..674af36b77 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SelfValidatingSortedQueueEntryList.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.queue; + +import junit.framework.Assert; + +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.SortedQueueEntryImpl.Colour; + +/** + * Test extension of SortedQueueEntryList that provides data structure validation tests. + * @see SortedQueueEntryList + */ +public class SelfValidatingSortedQueueEntryList extends SortedQueueEntryList +{ + public SelfValidatingSortedQueueEntryList(AMQQueue queue, String propertyName) + { + super(queue, propertyName); + } + + @Override /** Overridden to automatically check queue properties before and after. */ + public SortedQueueEntryImpl add(final ServerMessage message) + { + assertQueueProperties(); //before add + final SortedQueueEntryImpl result = super.add(message); + assertQueueProperties(); //after add + return result; + } + + @Override /** Overridden to automatically check queue properties before and after. */ + public void entryDeleted(SortedQueueEntryImpl entry) + { + assertQueueProperties(); //before delete + super.entryDeleted(entry); + assertQueueProperties(); //after delete + } + + public void assertQueueProperties() + { + assertRootIsBlack(); + assertTreeIntegrity(); + assertChildrenOfRedAreBlack(); + assertLeavesSameBlackPath(); + } + + public void assertRootIsBlack() + { + if(!isNodeColour(getRoot(), Colour.BLACK)) + { + Assert.fail("Root Not Black"); + } + } + + public void assertTreeIntegrity() + { + assertTreeIntegrity(getRoot()); + } + + public void assertTreeIntegrity(final SortedQueueEntryImpl node) + { + if(node == null) + { + return; + } + if(node.getLeft() != null) + { + if(node.getLeft().getParent() == node) + { + assertTreeIntegrity(node.getLeft()); + } + else + { + Assert.fail("Tree integrity compromised"); + } + } + if(node.getRight() != null) + { + if(node.getRight().getParent() == node) + { + assertTreeIntegrity(node.getRight()); + } + else + { + Assert.fail("Tree integrity compromised"); + } + + } + } + + public void assertLeavesSameBlackPath() + { + assertLeavesSameBlackPath(getRoot()); + } + + public int assertLeavesSameBlackPath(final SortedQueueEntryImpl node) + { + if(node == null) + { + return 1; + } + final int left = assertLeavesSameBlackPath(node.getLeft()); + final int right = assertLeavesSameBlackPath(node.getLeft()); + if(left == right) + { + return isNodeColour(node, Colour.BLACK) ? 1 + left : left; + } + else + { + Assert.fail("Unequal paths to leaves"); + return 1; //compiler + } + } + + public void assertChildrenOfRedAreBlack() + { + assertChildrenOfRedAreBlack(getRoot()); + } + + public void assertChildrenOfRedAreBlack(final SortedQueueEntryImpl node) + { + if(node == null) + { + return; + } + else if(node.getColour() == Colour.BLACK) + { + assertChildrenOfRedAreBlack(node.getLeft()); + assertChildrenOfRedAreBlack(node.getRight()); + } + else + { + if(isNodeColour(node.getLeft(), Colour.BLACK) + && isNodeColour(node.getRight(), Colour.BLACK)) + { + assertChildrenOfRedAreBlack(node.getLeft()); + assertChildrenOfRedAreBlack(node.getRight()); + } + else + { + Assert.fail("Children of Red are not both black"); + } + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java new file mode 100644 index 0000000000..3a41bb9c72 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueTest.java @@ -0,0 +1,1248 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.queue; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Matchers.contains; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +import java.util.Map; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.exchange.DirectExchange; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.queue.BaseQueue.PostEnqueueAction; +import org.apache.qpid.server.queue.SimpleAMQQueue.QueueEntryFilter; +import org.apache.qpid.server.subscription.MockSubscription; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class SimpleAMQQueueTest extends QpidTestCase +{ + + private SimpleAMQQueue _queue; + private VirtualHost _virtualHost; + private String _qname = "qname"; + private String _owner = "owner"; + private String _routingKey = "routing key"; + private DirectExchange _exchange; + private MockSubscription _subscription = new MockSubscription(); + private Map _arguments = null; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + + _virtualHost = BrokerTestHelper.createVirtualHost(getClass().getName()); + + _queue = (SimpleAMQQueue) _virtualHost.createQueue(UUIDGenerator.generateRandomUUID(), _qname, false, _owner, + false, false, false, _arguments); + + _exchange = (DirectExchange) _virtualHost.getExchange(ExchangeDefaults.DIRECT_EXCHANGE_NAME); + } + + @Override + public void tearDown() throws Exception + { + try + { + _queue.stop(); + _virtualHost.close(); + } + finally + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + } + + public void testCreateQueue() throws AMQException + { + _queue.stop(); + try + { + _queue = (SimpleAMQQueue) _virtualHost.createQueue(UUIDGenerator.generateRandomUUID(), null, + false, _owner, false, + false, false, _arguments); + assertNull("Queue was created", _queue); + } + catch (IllegalArgumentException e) + { + assertTrue("Exception was not about missing name", + e.getMessage().contains("name")); + } + + try + { + _queue = new SimpleAMQQueue(UUIDGenerator.generateRandomUUID(), _qname, false, _owner, false,false, null, Collections.EMPTY_MAP); + assertNull("Queue was created", _queue); + } + catch (IllegalArgumentException e) + { + assertTrue("Exception was not about missing vhost", + e.getMessage().contains("Host")); + } + + _queue = (SimpleAMQQueue) _virtualHost.createQueue(UUIDGenerator.generateRandomUUID(), + "differentName", false, + _owner, false, + false, false, _arguments); + assertNotNull("Queue was not created", _queue); + } + + public void testGetVirtualHost() + { + assertEquals("Virtual host was wrong", _virtualHost, _queue.getVirtualHost()); + } + + public void testBinding() throws AMQSecurityException, AMQInternalException + { + _exchange.addBinding(_routingKey, _queue, Collections.EMPTY_MAP); + + assertTrue("Routing key was not bound", + _exchange.isBound(_routingKey)); + assertTrue("Queue was not bound to key", + _exchange.isBound(_routingKey,_queue)); + assertEquals("Exchange binding count", 1, + _queue.getBindings().size()); + assertEquals("Wrong exchange bound", _routingKey, + _queue.getBindings().get(0).getBindingKey()); + assertEquals("Wrong exchange bound", _exchange, + _queue.getBindings().get(0).getExchange()); + + _exchange.removeBinding(_routingKey, _queue, Collections.EMPTY_MAP); + assertFalse("Routing key was still bound", + _exchange.isBound(_routingKey)); + + } + + public void testRegisterSubscriptionThenEnqueueMessage() throws AMQException + { + // Check adding a subscription adds it to the queue + _queue.registerSubscription(_subscription, false); + assertEquals("Subscription did not get queue", _queue, + _subscription.getQueue()); + assertEquals("Queue does not have consumer", 1, + _queue.getConsumerCount()); + assertEquals("Queue does not have active consumer", 1, + _queue.getActiveConsumerCount()); + + // Check sending a message ends up with the subscriber + ServerMessage messageA = createMessage(new Long(24)); + _queue.enqueue(messageA); + try + { + Thread.sleep(2000L); + } + catch(InterruptedException e) + { + } + assertEquals(messageA, _subscription.getQueueContext().getLastSeenEntry().getMessage()); + assertNull(((QueueContext)_subscription.getQueueContext()).getReleasedEntry()); + + // Check removing the subscription removes it's information from the queue + _queue.unregisterSubscription(_subscription); + assertTrue("Subscription still had queue", _subscription.isClosed()); + assertFalse("Queue still has consumer", 1 == _queue.getConsumerCount()); + assertFalse("Queue still has active consumer", + 1 == _queue.getActiveConsumerCount()); + + ServerMessage messageB = createMessage(new Long (25)); + _queue.enqueue(messageB); + assertNull(_subscription.getQueueContext()); + + } + + public void testEnqueueMessageThenRegisterSubscription() throws AMQException, InterruptedException + { + ServerMessage messageA = createMessage(new Long(24)); + _queue.enqueue(messageA); + _queue.registerSubscription(_subscription, false); + Thread.sleep(150); + assertEquals(messageA, _subscription.getQueueContext().getLastSeenEntry().getMessage()); + assertNull("There should be no releasedEntry after an enqueue", ((QueueContext)_subscription.getQueueContext()).getReleasedEntry()); + } + + /** + * Tests enqueuing two messages. + */ + public void testEnqueueTwoMessagesThenRegisterSubscription() throws Exception + { + ServerMessage messageA = createMessage(new Long(24)); + ServerMessage messageB = createMessage(new Long(25)); + _queue.enqueue(messageA); + _queue.enqueue(messageB); + _queue.registerSubscription(_subscription, false); + Thread.sleep(150); + assertEquals(messageB, _subscription.getQueueContext().getLastSeenEntry().getMessage()); + assertNull("There should be no releasedEntry after enqueues", ((QueueContext)_subscription.getQueueContext()).getReleasedEntry()); + } + + /** + * Tests that a released queue entry is resent to the subscriber. Verifies also that the + * QueueContext._releasedEntry is reset to null after the entry has been reset. + */ + public void testReleasedMessageIsResentToSubscriber() throws Exception + { + _queue.registerSubscription(_subscription, false); + + final ArrayList queueEntries = new ArrayList(); + PostEnqueueAction postEnqueueAction = new PostEnqueueAction() + { + public void onEnqueue(QueueEntry entry) + { + queueEntries.add(entry); + } + }; + + ServerMessage messageA = createMessage(new Long(24)); + ServerMessage messageB = createMessage(new Long(25)); + ServerMessage messageC = createMessage(new Long(26)); + + /* Enqueue three messages */ + + _queue.enqueue(messageA, postEnqueueAction); + _queue.enqueue(messageB, postEnqueueAction); + _queue.enqueue(messageC, postEnqueueAction); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to subscription", 3, _subscription.getMessages().size()); + assertFalse("Redelivery flag should not be set", queueEntries.get(0).isRedelivered()); + assertFalse("Redelivery flag should not be set", queueEntries.get(1).isRedelivered()); + assertFalse("Redelivery flag should not be set", queueEntries.get(2).isRedelivered()); + + /* Now release the first message only, causing it to be requeued */ + + queueEntries.get(0).release(); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to subscription", 4, _subscription.getMessages().size()); + assertTrue("Redelivery flag should now be set", queueEntries.get(0).isRedelivered()); + assertFalse("Redelivery flag should remain be unset", queueEntries.get(1).isRedelivered()); + assertFalse("Redelivery flag should remain be unset",queueEntries.get(2).isRedelivered()); + assertNull("releasedEntry should be cleared after requeue processed", ((QueueContext)_subscription.getQueueContext()).getReleasedEntry()); + } + + /** + * Tests that a released message that becomes expired is not resent to the subscriber. + * This tests ensures that SimpleAMQQueueEntry.getNextAvailableEntry avoids expired entries. + * Verifies also that the QueueContext._releasedEntry is reset to null after the entry has been reset. + */ + public void testReleaseMessageThatBecomesExpiredIsNotRedelivered() throws Exception + { + _queue.registerSubscription(_subscription, false); + + final ArrayList queueEntries = new ArrayList(); + PostEnqueueAction postEnqueueAction = new PostEnqueueAction() + { + public void onEnqueue(QueueEntry entry) + { + queueEntries.add(entry); + } + }; + + /* Enqueue one message with expiration set for a short time in the future */ + + ServerMessage messageA = createMessage(new Long(24)); + int messageExpirationOffset = 200; + final long expiration = System.currentTimeMillis() + messageExpirationOffset; + when(messageA.getExpiration()).thenReturn(expiration); + + _queue.enqueue(messageA, postEnqueueAction); + + int subFlushWaitTime = 150; + Thread.sleep(subFlushWaitTime); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to subscription", 1, _subscription.getMessages().size()); + assertFalse("Redelivery flag should not be set", queueEntries.get(0).isRedelivered()); + + /* Wait a little more to be sure that message will have expired, then release the first message only, causing it to be requeued */ + Thread.sleep(messageExpirationOffset - subFlushWaitTime + 10); + queueEntries.get(0).release(); + + Thread.sleep(subFlushWaitTime); // Work done by SubFlushRunner/QueueRunner Threads + + assertTrue("Expecting the queue entry to be now expired", queueEntries.get(0).expired()); + assertEquals("Total number of messages sent should not have changed", 1, _subscription.getMessages().size()); + assertFalse("Redelivery flag should not be set", queueEntries.get(0).isRedelivered()); + assertNull("releasedEntry should be cleared after requeue processed", ((QueueContext)_subscription.getQueueContext()).getReleasedEntry()); + + } + + /** + * Tests that if a client releases entries 'out of order' (the order + * used by QueueEntryImpl.compareTo) that messages are still resent + * successfully. Specifically this test ensures the {@see SimpleAMQQueue#requeue()} + * can correctly move the _releasedEntry to an earlier position in the QueueEntry list. + */ + public void testReleasedOutOfComparableOrderAreRedelivered() throws Exception + { + _queue.registerSubscription(_subscription, false); + + final ArrayList queueEntries = new ArrayList(); + PostEnqueueAction postEnqueueAction = new PostEnqueueAction() + { + public void onEnqueue(QueueEntry entry) + { + queueEntries.add(entry); + } + }; + + ServerMessage messageA = createMessage(new Long(24)); + ServerMessage messageB = createMessage(new Long(25)); + ServerMessage messageC = createMessage(new Long(26)); + + /* Enqueue three messages */ + + _queue.enqueue(messageA, postEnqueueAction); + _queue.enqueue(messageB, postEnqueueAction); + _queue.enqueue(messageC, postEnqueueAction); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to subscription", 3, _subscription.getMessages().size()); + assertFalse("Redelivery flag should not be set", queueEntries.get(0).isRedelivered()); + assertFalse("Redelivery flag should not be set", queueEntries.get(1).isRedelivered()); + assertFalse("Redelivery flag should not be set", queueEntries.get(2).isRedelivered()); + + /* Now release the third and first message only, causing it to be requeued */ + + queueEntries.get(2).release(); + queueEntries.get(0).release(); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to subscription", 5, _subscription.getMessages().size()); + assertTrue("Redelivery flag should now be set", queueEntries.get(0).isRedelivered()); + assertFalse("Redelivery flag should remain be unset", queueEntries.get(1).isRedelivered()); + assertTrue("Redelivery flag should now be set",queueEntries.get(2).isRedelivered()); + assertNull("releasedEntry should be cleared after requeue processed", ((QueueContext)_subscription.getQueueContext()).getReleasedEntry()); + } + + + /** + * Tests that a release requeues an entry for a queue with multiple subscriptions. Verifies that a + * requeue resends a message to a single subscriber. + */ + public void testReleaseForQueueWithMultipleSubscriptions() throws Exception + { + MockSubscription subscription1 = new MockSubscription(); + MockSubscription subscription2 = new MockSubscription(); + + _queue.registerSubscription(subscription1, false); + _queue.registerSubscription(subscription2, false); + + final ArrayList queueEntries = new ArrayList(); + PostEnqueueAction postEnqueueAction = new PostEnqueueAction() + { + public void onEnqueue(QueueEntry entry) + { + queueEntries.add(entry); + } + }; + + ServerMessage messageA = createMessage(new Long(24)); + ServerMessage messageB = createMessage(new Long(25)); + + /* Enqueue two messages */ + + _queue.enqueue(messageA, postEnqueueAction); + _queue.enqueue(messageB, postEnqueueAction); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to both after enqueue", 2, subscription1.getMessages().size() + subscription2.getMessages().size()); + + /* Now release the first message only, causing it to be requeued */ + queueEntries.get(0).release(); + + Thread.sleep(150); // Work done by SubFlushRunner/QueueRunner Threads + + assertEquals("Unexpected total number of messages sent to both subscriptions after release", 3, subscription1.getMessages().size() + subscription2.getMessages().size()); + assertNull("releasedEntry should be cleared after requeue processed", ((QueueContext)subscription1.getQueueContext()).getReleasedEntry()); + assertNull("releasedEntry should be cleared after requeue processed", ((QueueContext)subscription2.getQueueContext()).getReleasedEntry()); + } + + public void testExclusiveConsumer() throws AMQException + { + // Check adding an exclusive subscription adds it to the queue + _queue.registerSubscription(_subscription, true); + assertEquals("Subscription did not get queue", _queue, + _subscription.getQueue()); + assertEquals("Queue does not have consumer", 1, + _queue.getConsumerCount()); + assertEquals("Queue does not have active consumer", 1, + _queue.getActiveConsumerCount()); + + // Check sending a message ends up with the subscriber + ServerMessage messageA = createMessage(new Long(24)); + _queue.enqueue(messageA); + try + { + Thread.sleep(2000L); + } + catch (InterruptedException e) + { + } + assertEquals(messageA, _subscription.getQueueContext().getLastSeenEntry().getMessage()); + + // Check we cannot add a second subscriber to the queue + Subscription subB = new MockSubscription(); + Exception ex = null; + try + { + _queue.registerSubscription(subB, false); + } + catch (AMQException e) + { + ex = e; + } + assertNotNull(ex); + + // Check we cannot add an exclusive subscriber to a queue with an + // existing subscription + _queue.unregisterSubscription(_subscription); + _queue.registerSubscription(_subscription, false); + try + { + _queue.registerSubscription(subB, true); + } + catch (AMQException e) + { + ex = e; + } + assertNotNull(ex); + } + + public void testAutoDeleteQueue() throws Exception + { + _queue.stop(); + _queue = new SimpleAMQQueue(UUIDGenerator.generateRandomUUID(), _qname, false, null, true, false, _virtualHost, Collections.EMPTY_MAP); + _queue.setDeleteOnNoConsumers(true); + _queue.registerSubscription(_subscription, false); + ServerMessage message = createMessage(new Long(25)); + _queue.enqueue(message); + _queue.unregisterSubscription(_subscription); + assertTrue("Queue was not deleted when subscription was removed", + _queue.isDeleted()); + } + + public void testResend() throws Exception + { + _queue.registerSubscription(_subscription, false); + Long id = new Long(26); + ServerMessage message = createMessage(id); + _queue.enqueue(message); + QueueEntry entry = _subscription.getQueueContext().getLastSeenEntry(); + entry.setRedelivered(); + _queue.resend(entry, _subscription); + + } + + public void testGetFirstMessageId() throws Exception + { + // Create message + Long messageId = new Long(23); + ServerMessage message = createMessage(messageId); + + // Put message on queue + _queue.enqueue(message); + // Get message id + Long testmsgid = _queue.getMessagesOnTheQueue(1).get(0); + + // Check message id + assertEquals("Message ID was wrong", messageId, testmsgid); + } + + public void testGetFirstFiveMessageIds() throws Exception + { + for (int i = 0 ; i < 5; i++) + { + // Create message + Long messageId = new Long(i); + ServerMessage message = createMessage(messageId); + // Put message on queue + _queue.enqueue(message); + } + // Get message ids + List msgids = _queue.getMessagesOnTheQueue(5); + + // Check message id + for (int i = 0; i < 5; i++) + { + Long messageId = new Long(i); + assertEquals("Message ID was wrong", messageId, msgids.get(i)); + } + } + + public void testGetLastFiveMessageIds() throws Exception + { + for (int i = 0 ; i < 10; i++) + { + // Create message + Long messageId = new Long(i); + ServerMessage message = createMessage(messageId); + // Put message on queue + _queue.enqueue(message); + } + // Get message ids + List msgids = _queue.getMessagesOnTheQueue(5, 5); + + // Check message id + for (int i = 0; i < 5; i++) + { + Long messageId = new Long(i+5); + assertEquals("Message ID was wrong", messageId, msgids.get(i)); + } + } + + public void testGetMessagesRangeOnTheQueue() throws Exception + { + for (int i = 1 ; i <= 10; i++) + { + // Create message + Long messageId = new Long(i); + ServerMessage message = createMessage(messageId); + // Put message on queue + _queue.enqueue(message); + } + + // Get non-existent 0th QueueEntry & check returned list was empty + // (the position parameters in this method are indexed from 1) + List entries = _queue.getMessagesRangeOnTheQueue(0, 0); + assertTrue(entries.size() == 0); + + // Check that when 'from' is 0 it is ignored and the range continues from 1 + entries = _queue.getMessagesRangeOnTheQueue(0, 2); + assertTrue(entries.size() == 2); + long msgID = entries.get(0).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 1L); + msgID = entries.get(1).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 2L); + + // Check that when 'from' is greater than 'to' the returned list is empty + entries = _queue.getMessagesRangeOnTheQueue(5, 4); + assertTrue(entries.size() == 0); + + // Get first QueueEntry & check id + entries = _queue.getMessagesRangeOnTheQueue(1, 1); + assertTrue(entries.size() == 1); + msgID = entries.get(0).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 1L); + + // Get 5th,6th,7th entries and check id's + entries = _queue.getMessagesRangeOnTheQueue(5, 7); + assertTrue(entries.size() == 3); + msgID = entries.get(0).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 5L); + msgID = entries.get(1).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 6L); + msgID = entries.get(2).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 7L); + + // Get 10th QueueEntry & check id + entries = _queue.getMessagesRangeOnTheQueue(10, 10); + assertTrue(entries.size() == 1); + msgID = entries.get(0).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 10L); + + // Get non-existent 11th QueueEntry & check returned set was empty + entries = _queue.getMessagesRangeOnTheQueue(11, 11); + assertTrue(entries.size() == 0); + + // Get 9th,10th, and non-existent 11th entries & check result is of size 2 with correct IDs + entries = _queue.getMessagesRangeOnTheQueue(9, 11); + assertTrue(entries.size() == 2); + msgID = entries.get(0).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 9L); + msgID = entries.get(1).getMessage().getMessageNumber(); + assertEquals("Message ID was wrong", msgID, 10L); + } + + + /** + * processQueue() is used when asynchronously delivering messages to + * subscriptions which could not be delivered immediately during the + * enqueue() operation. + * + * A defect within the method would mean that delivery of these messages may + * not occur should the Runner stop before all messages have been processed. + * Such a defect was discovered when Selectors were used such that one and + * only one subscription can/will accept any given messages, but multiple + * subscriptions are present, and one of the earlier subscriptions receives + * more messages than the others. + * + * This test is to validate that the processQueue() method is able to + * correctly deliver all of the messages present for asynchronous delivery + * to subscriptions in such a scenario. + */ + public void testProcessQueueWithUniqueSelectors() throws Exception + { + TestSimpleQueueEntryListFactory factory = new TestSimpleQueueEntryListFactory(); + SimpleAMQQueue testQueue = new SimpleAMQQueue(UUIDGenerator.generateRandomUUID(), "testQueue", false,"testOwner", + false, false, _virtualHost, factory, null) + { + @Override + public void deliverAsync(Subscription sub) + { + // do nothing, i.e prevent deliveries by the SubFlushRunner + // when registering the new subscriptions + } + }; + + // retrieve the QueueEntryList the queue creates and insert the test + // messages, thus avoiding straight-through delivery attempts during + //enqueue() process. + QueueEntryList list = factory.getQueueEntryList(); + assertNotNull("QueueEntryList should have been created", list); + + QueueEntry msg1 = list.add(createMessage(1L)); + QueueEntry msg2 = list.add(createMessage(2L)); + QueueEntry msg3 = list.add(createMessage(3L)); + QueueEntry msg4 = list.add(createMessage(4L)); + QueueEntry msg5 = list.add(createMessage(5L)); + + // Create lists of the entries each subscription should be interested + // in.Bias over 50% of the messages to the first subscription so that + // the later subscriptions reject them and report being done before + // the first subscription as the processQueue method proceeds. + List msgListSub1 = createEntriesList(msg1, msg2, msg3); + List msgListSub2 = createEntriesList(msg4); + List msgListSub3 = createEntriesList(msg5); + + MockSubscription sub1 = new MockSubscription(msgListSub1); + MockSubscription sub2 = new MockSubscription(msgListSub2); + MockSubscription sub3 = new MockSubscription(msgListSub3); + + // register the subscriptions + testQueue.registerSubscription(sub1, false); + testQueue.registerSubscription(sub2, false); + testQueue.registerSubscription(sub3, false); + + //check that no messages have been delivered to the + //subscriptions during registration + assertEquals("No messages should have been delivered yet", 0, sub1.getMessages().size()); + assertEquals("No messages should have been delivered yet", 0, sub2.getMessages().size()); + assertEquals("No messages should have been delivered yet", 0, sub3.getMessages().size()); + + // call processQueue to deliver the messages + testQueue.processQueue(new QueueRunner(testQueue) + { + @Override + public void run() + { + // we dont actually want/need this runner to do any work + // because we we are already doing it! + } + }); + + // check expected messages delivered to correct consumers + verifyRecievedMessages(msgListSub1, sub1.getMessages()); + verifyRecievedMessages(msgListSub2, sub2.getMessages()); + verifyRecievedMessages(msgListSub3, sub3.getMessages()); + } + + /** + * Tests that dequeued message is not present in the list returned form + * {@link SimpleAMQQueue#getMessagesOnTheQueue()} + */ + public void testGetMessagesOnTheQueueWithDequeuedEntry() + { + int messageNumber = 4; + int dequeueMessageIndex = 1; + + // send test messages into a test queue + enqueueGivenNumberOfMessages(_queue, messageNumber); + + // dequeue message + dequeueMessage(_queue, dequeueMessageIndex); + + // get messages on the queue + List entries = _queue.getMessagesOnTheQueue(); + + // assert queue entries + assertEquals(messageNumber - 1, entries.size()); + int expectedId = 0; + for (int i = 0; i < messageNumber - 1; i++) + { + Long id = ( entries.get(i).getMessage()).getMessageNumber(); + if (i == dequeueMessageIndex) + { + assertFalse("Message with id " + dequeueMessageIndex + + " was dequeued and should not be returned by method getMessagesOnTheQueue!", + new Long(expectedId).equals(id)); + expectedId++; + } + assertEquals("Expected message with id " + expectedId + " but got message with id " + id, + new Long(expectedId), id); + expectedId++; + } + } + + /** + * Tests that dequeued message is not present in the list returned form + * {@link SimpleAMQQueue#getMessagesOnTheQueue(QueueEntryFilter)} + */ + public void testGetMessagesOnTheQueueByQueueEntryFilterWithDequeuedEntry() + { + int messageNumber = 4; + int dequeueMessageIndex = 1; + + // send test messages into a test queue + enqueueGivenNumberOfMessages(_queue, messageNumber); + + // dequeue message + dequeueMessage(_queue, dequeueMessageIndex); + + // get messages on the queue with filter accepting all available messages + List entries = _queue.getMessagesOnTheQueue(new QueueEntryFilter() + { + public boolean accept(QueueEntry entry) + { + return true; + } + + public boolean filterComplete() + { + return false; + } + }); + + // assert entries on the queue + assertEquals(messageNumber - 1, entries.size()); + int expectedId = 0; + for (int i = 0; i < messageNumber - 1; i++) + { + Long id = (entries.get(i).getMessage()).getMessageNumber(); + if (i == dequeueMessageIndex) + { + assertFalse("Message with id " + dequeueMessageIndex + + " was dequeued and should not be returned by method getMessagesOnTheQueue!", + new Long(expectedId).equals(id)); + expectedId++; + } + assertEquals("Expected message with id " + expectedId + " but got message with id " + id, + new Long(expectedId), id); + expectedId++; + } + } + + + + /** + * Tests that dequeued message on the top is not accounted and next message + * is deleted from the queue on invocation of + * {@link SimpleAMQQueue#deleteMessageFromTop()} + */ + public void testDeleteMessageFromTopWithDequeuedEntryOnTop() + { + int messageNumber = 4; + int dequeueMessageIndex = 0; + + // put messages into a test queue + enqueueGivenNumberOfMessages(_queue, messageNumber); + + // dequeue message on top + dequeueMessage(_queue, dequeueMessageIndex); + + //delete message from top + _queue.deleteMessageFromTop(); + + //get queue netries + List entries = _queue.getMessagesOnTheQueue(); + + // assert queue entries + assertNotNull("Null is returned from getMessagesOnTheQueue", entries); + assertEquals("Expected " + (messageNumber - 2) + " number of messages but recieved " + entries.size(), + messageNumber - 2, entries.size()); + assertEquals("Expected first entry with id 2", 2l, + (entries.get(0).getMessage()).getMessageNumber()); + } + + /** + * Tests that all messages including dequeued one are deleted from the queue + * on invocation of {@link SimpleAMQQueue#clearQueue()} + */ + public void testClearQueueWithDequeuedEntry() + { + int messageNumber = 4; + int dequeueMessageIndex = 1; + + // put messages into a test queue + enqueueGivenNumberOfMessages(_queue, messageNumber); + + // dequeue message on a test queue + dequeueMessage(_queue, dequeueMessageIndex); + + // clean queue + try + { + _queue.clearQueue(); + } + catch (AMQException e) + { + fail("Failure to clear queue:" + e.getMessage()); + } + + // get queue entries + List entries = _queue.getMessagesOnTheQueue(); + + // assert queue entries + assertNotNull(entries); + assertEquals(0, entries.size()); + } + + /** + * Tests whether dequeued entry is sent to subscriber in result of + * invocation of {@link SimpleAMQQueue#processQueue(QueueRunner)} + */ + public void testProcessQueueWithDequeuedEntry() + { + // total number of messages to send + int messageNumber = 4; + int dequeueMessageIndex = 1; + + // create queue with overridden method deliverAsync + SimpleAMQQueue testQueue = new SimpleAMQQueue(UUIDGenerator.generateRandomUUID(), "test", + false, "testOwner", false, false, _virtualHost, null) + { + @Override + public void deliverAsync(Subscription sub) + { + // do nothing + } + }; + + // put messages + List entries = enqueueGivenNumberOfMessages(testQueue, messageNumber); + + // dequeue message + dequeueMessage(testQueue, dequeueMessageIndex); + + // latch to wait for message receipt + final CountDownLatch latch = new CountDownLatch(messageNumber -1); + + // create a subscription + MockSubscription subscription = new MockSubscription() + { + /** + * Send a message and decrement latch + * @param entry + * @param batch + */ + public void send(QueueEntry entry, boolean batch) throws AMQException + { + super.send(entry, batch); + latch.countDown(); + } + }; + + try + { + // subscribe + testQueue.registerSubscription(subscription, false); + + // process queue + testQueue.processQueue(new QueueRunner(testQueue) + { + public void run() + { + // do nothing + } + }); + } + catch (AMQException e) + { + fail("Failure to process queue:" + e.getMessage()); + } + // wait up to 1 minute for message receipt + try + { + latch.await(1, TimeUnit.MINUTES); + } + catch (InterruptedException e1) + { + Thread.currentThread().interrupt(); + } + List expected = createEntriesList(entries.get(0), entries.get(2), entries.get(3)); + verifyRecievedMessages(expected, subscription.getMessages()); + } + + /** + * Tests that entry in dequeued state are not enqueued and not delivered to subscription + */ + public void testEnqueueDequeuedEntry() + { + // create a queue where each even entry is considered a dequeued + SimpleAMQQueue queue = new SimpleAMQQueue(UUIDGenerator.generateRandomUUID(), "test", false, + "testOwner", false, false, _virtualHost, new QueueEntryListFactory() + { + public QueueEntryList createQueueEntryList(AMQQueue queue) + { + /** + * Override SimpleQueueEntryList to create a dequeued + * entries for messages with even id + */ + return new SimpleQueueEntryList(queue) + { + /** + * Entries with even message id are considered + * dequeued! + */ + protected SimpleQueueEntryImpl createQueueEntry(final ServerMessage message) + { + return new SimpleQueueEntryImpl(this, message) + { + public boolean isDequeued() + { + return (message.getMessageNumber() % 2 == 0); + } + + public boolean isDispensed() + { + return (message.getMessageNumber() % 2 == 0); + } + + public boolean isAvailable() + { + return !(message.getMessageNumber() % 2 == 0); + } + + @Override + public boolean acquire(Subscription sub) + { + if(message.getMessageNumber() % 2 == 0) + { + return false; + } + else + { + return super.acquire(sub); + } + } + }; + } + }; + } + }, null); + // create a subscription + MockSubscription subscription = new MockSubscription(); + + // register subscription + try + { + queue.registerSubscription(subscription, false); + } + catch (AMQException e) + { + fail("Failure to register subscription:" + e.getMessage()); + } + + // put test messages into a queue + putGivenNumberOfMessages(queue, 4); + + // assert received messages + List messages = subscription.getMessages(); + assertEquals("Only 2 messages should be returned", 2, messages.size()); + assertEquals("ID of first message should be 1", 1l, + (messages.get(0).getMessage()).getMessageNumber()); + assertEquals("ID of second message should be 3", 3l, + (messages.get(1).getMessage()).getMessageNumber()); + } + + public void testActiveConsumerCount() throws Exception + { + final SimpleAMQQueue queue = new SimpleAMQQueue(UUIDGenerator.generateRandomUUID(), "testActiveConsumerCount", false, + "testOwner", false, false, _virtualHost, new SimpleQueueEntryList.Factory(), null); + + //verify adding an active subscription increases the count + final MockSubscription subscription1 = new MockSubscription(); + subscription1.setActive(true); + assertEquals("Unexpected active consumer count", 0, queue.getActiveConsumerCount()); + queue.registerSubscription(subscription1, false); + assertEquals("Unexpected active consumer count", 1, queue.getActiveConsumerCount()); + + //verify adding an inactive subscription doesn't increase the count + final MockSubscription subscription2 = new MockSubscription(); + subscription2.setActive(false); + assertEquals("Unexpected active consumer count", 1, queue.getActiveConsumerCount()); + queue.registerSubscription(subscription2, false); + assertEquals("Unexpected active consumer count", 1, queue.getActiveConsumerCount()); + + //verify behaviour in face of expected state changes: + + //verify a subscription going suspended->active increases the count + queue.stateChange(subscription2, Subscription.State.SUSPENDED, Subscription.State.ACTIVE); + assertEquals("Unexpected active consumer count", 2, queue.getActiveConsumerCount()); + + //verify a subscription going active->suspended decreases the count + queue.stateChange(subscription2, Subscription.State.ACTIVE, Subscription.State.SUSPENDED); + assertEquals("Unexpected active consumer count", 1, queue.getActiveConsumerCount()); + + //verify a subscription going suspended->closed doesn't change the count + queue.stateChange(subscription2, Subscription.State.SUSPENDED, Subscription.State.CLOSED); + assertEquals("Unexpected active consumer count", 1, queue.getActiveConsumerCount()); + + //verify a subscription going active->closed decreases the count + queue.stateChange(subscription2, Subscription.State.ACTIVE, Subscription.State.CLOSED); + assertEquals("Unexpected active consumer count", 0, queue.getActiveConsumerCount()); + + //verify behaviour in face of unexpected state changes: + + //verify a subscription going closed->active increases the count + queue.stateChange(subscription2, Subscription.State.CLOSED, Subscription.State.ACTIVE); + assertEquals("Unexpected active consumer count", 1, queue.getActiveConsumerCount()); + + //verify a subscription going active->active doesn't change the count + queue.stateChange(subscription2, Subscription.State.ACTIVE, Subscription.State.ACTIVE); + assertEquals("Unexpected active consumer count", 1, queue.getActiveConsumerCount()); + + //verify a subscription going closed->suspended doesn't change the count + queue.stateChange(subscription2, Subscription.State.CLOSED, Subscription.State.SUSPENDED); + assertEquals("Unexpected active consumer count", 1, queue.getActiveConsumerCount()); + + //verify a subscription going suspended->suspended doesn't change the count + queue.stateChange(subscription2, Subscription.State.SUSPENDED, Subscription.State.SUSPENDED); + assertEquals("Unexpected active consumer count", 1, queue.getActiveConsumerCount()); + } + + public void testNotificationFiredOnEnqueue() throws Exception + { + AMQQueue.NotificationListener listener = mock(AMQQueue.NotificationListener.class); + + _queue.setNotificationListener(listener); + _queue.setMaximumMessageCount(2); + + _queue.enqueue(createMessage(new Long(24))); + verifyZeroInteractions(listener); + + _queue.enqueue(createMessage(new Long(25))); + + verify(listener, atLeastOnce()).notifyClients(eq(NotificationCheck.MESSAGE_COUNT_ALERT), eq(_queue), contains("Maximum count on queue threshold")); + } + + public void testNotificationFiredAsync() throws Exception + { + AMQQueue.NotificationListener listener = mock(AMQQueue.NotificationListener.class); + + _queue.enqueue(createMessage(new Long(24))); + _queue.enqueue(createMessage(new Long(25))); + _queue.enqueue(createMessage(new Long(26))); + + _queue.setNotificationListener(listener); + _queue.setMaximumMessageCount(2); + + verifyZeroInteractions(listener); + + _queue.checkMessageStatus(); + + verify(listener, atLeastOnce()).notifyClients(eq(NotificationCheck.MESSAGE_COUNT_ALERT), eq(_queue), contains("Maximum count on queue threshold")); + } + + /** + * A helper method to put given number of messages into queue + *

+ * All messages are asserted that they are present on queue + * + * @param queue + * queue to put messages into + * @param messageNumber + * number of messages to put into queue + */ + private List enqueueGivenNumberOfMessages(AMQQueue queue, int messageNumber) + { + putGivenNumberOfMessages(queue, messageNumber); + + // make sure that all enqueued messages are on the queue + List entries = queue.getMessagesOnTheQueue(); + assertEquals(messageNumber, entries.size()); + for (int i = 0; i < messageNumber; i++) + { + assertEquals((long)i, (entries.get(i).getMessage()).getMessageNumber()); + } + return entries; + } + + /** + * A helper method to put given number of messages into queue + *

+ * Queue is not checked if messages are added into queue + * + * @param queue + * queue to put messages into + * @param messageNumber + * number of messages to put into queue + * @param queue + * @param messageNumber + */ + private void putGivenNumberOfMessages(AMQQueue queue, int messageNumber) + { + for (int i = 0; i < messageNumber; i++) + { + // Create message + Long messageId = new Long(i); + ServerMessage message = null; + try + { + message = createMessage(messageId); + } + catch (AMQException e) + { + fail("Failure to create a test message:" + e.getMessage()); + } + // Put message on queue + try + { + queue.enqueue(message); + } + catch (AMQException e) + { + fail("Failure to put message on queue:" + e.getMessage()); + } + } + try + { + Thread.sleep(2000L); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + } + + /** + * A helper method to dequeue an entry on queue with given index + * + * @param queue + * queue to dequeue message on + * @param dequeueMessageIndex + * entry index to dequeue. + */ + private QueueEntry dequeueMessage(AMQQueue queue, int dequeueMessageIndex) + { + List entries = queue.getMessagesOnTheQueue(); + QueueEntry entry = entries.get(dequeueMessageIndex); + entry.acquire(); + entry.dequeue(); + assertTrue(entry.isDequeued()); + return entry; + } + + private List createEntriesList(QueueEntry... entries) + { + ArrayList entriesList = new ArrayList(); + for (QueueEntry entry : entries) + { + entriesList.add(entry); + } + return entriesList; + } + + private void verifyRecievedMessages(List expected, + List delivered) + { + assertEquals("Consumer did not receive the expected number of messages", + expected.size(), delivered.size()); + + for (QueueEntry msg : expected) + { + assertTrue("Consumer did not recieve msg: " + + msg.getMessage().getMessageNumber(), delivered.contains(msg)); + } + } + + public SimpleAMQQueue getQueue() + { + return _queue; + } + + public MockSubscription getSubscription() + { + return _subscription; + } + + public Map getArguments() + { + return _arguments; + } + + public void setArguments(Map arguments) + { + _arguments = arguments; + } + + + protected ServerMessage createMessage(Long id) throws AMQException + { + ServerMessage message = mock(ServerMessage.class); + when(message.getMessageNumber()).thenReturn(id); + + MessageReference ref = mock(MessageReference.class); + when(ref.getMessage()).thenReturn(message); + + AMQMessageHeader hdr = mock(AMQMessageHeader.class); + when(message.getMessageHeader()).thenReturn(hdr); + + when(message.newReference()).thenReturn(ref); + + return message; + } + + class TestSimpleQueueEntryListFactory implements QueueEntryListFactory + { + QueueEntryList _list; + + public QueueEntryList createQueueEntryList(AMQQueue queue) + { + _list = new SimpleQueueEntryList(queue); + return _list; + } + + public QueueEntryList getQueueEntryList() + { + return _list; + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java new file mode 100644 index 0000000000..c115af5a38 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleAMQQueueThreadPoolTest.java @@ -0,0 +1,69 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.pool.ReferenceCountingExecutorService; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; + +public class SimpleAMQQueueThreadPoolTest extends QpidTestCase +{ + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + } + + @Override + public void tearDown() throws Exception + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + + public void test() throws Exception + { + int initialCount = ReferenceCountingExecutorService.getInstance().getReferenceCount(); + VirtualHost test = BrokerTestHelper.createVirtualHost("test"); + + try + { + SimpleAMQQueue queue = (SimpleAMQQueue) + test.createQueue(UUIDGenerator.generateRandomUUID(), "test", false, "owner", false, false, false, null); + + assertFalse("Creation did not start Pool.", ReferenceCountingExecutorService.getInstance().getPool().isShutdown()); + + assertEquals("References not increased", initialCount + 1, ReferenceCountingExecutorService.getInstance().getReferenceCount()); + + queue.stop(); + + assertEquals("References not decreased", initialCount , ReferenceCountingExecutorService.getInstance().getReferenceCount()); + } + finally + { + test.close(); + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryImplTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryImplTest.java new file mode 100644 index 0000000000..c2c2fc4b98 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryImplTest.java @@ -0,0 +1,83 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.ServerMessage; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SimpleQueueEntryImplTest extends QueueEntryImplTestBase { + + private SimpleQueueEntryList queueEntryList = new SimpleQueueEntryList(new MockAMQQueue("test")); + + public QueueEntryImpl getQueueEntryImpl(int msgId) throws AMQException { + ServerMessage message = mock(ServerMessage.class); + when(message.getMessageNumber()).thenReturn((long)msgId); + return queueEntryList.add(message); + } + + public void testCompareTo() + { + assertTrue(_queueEntry.compareTo(_queueEntry2) < 0); + assertTrue(_queueEntry2.compareTo(_queueEntry3) < 0); + assertTrue(_queueEntry.compareTo(_queueEntry3) < 0); + + assertTrue(_queueEntry2.compareTo(_queueEntry) > 0); + assertTrue(_queueEntry3.compareTo(_queueEntry2) > 0); + assertTrue(_queueEntry3.compareTo(_queueEntry) > 0); + + assertTrue(_queueEntry.compareTo(_queueEntry) == 0); + assertTrue(_queueEntry2.compareTo(_queueEntry2) == 0); + assertTrue(_queueEntry3.compareTo(_queueEntry3) == 0); + } + + public void testTraverseWithNoDeletedEntries() + { + QueueEntry current = _queueEntry; + + current = current.getNextValidEntry(); + assertSame("Unexpected current entry",_queueEntry2, current); + + current = current.getNextValidEntry(); + assertSame("Unexpected current entry",_queueEntry3, current); + + current = current.getNextValidEntry(); + assertNull(current); + + } + + public void testTraverseWithDeletedEntries() + { + // Delete 2nd queue entry + _queueEntry2.delete(); + assertTrue(_queueEntry2.isDeleted()); + + QueueEntry current = _queueEntry; + + current = current.getNextValidEntry(); + assertSame("Unexpected current entry",_queueEntry3, current); + + current = current.getNextValidEntry(); + assertNull(current); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryListTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryListTest.java new file mode 100644 index 0000000000..63b3a7d165 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SimpleQueueEntryListTest.java @@ -0,0 +1,247 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* +*/ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SimpleQueueEntryListTest extends QueueEntryListTestBase +{ + private SimpleQueueEntryList _sqel; + + private static final String SCAVENGE_PROP = "qpid.queue.scavenge_count"; + private String oldScavengeValue = null; + + @Override + protected void setUp() + { + oldScavengeValue = System.setProperty(SCAVENGE_PROP, "9"); + _sqel = new SimpleQueueEntryList(_testQueue); + for(int i = 1; i <= 100; i++) + { + final ServerMessage message = mock(ServerMessage.class); + when(message.getMessageNumber()).thenReturn((long) i); + MessageReference ref = mock(MessageReference.class); + when(ref.getMessage()).thenReturn(message); + when(message.newReference()).thenReturn(ref); + + final QueueEntry bleh = _sqel.add(message); + assertNotNull("QE should not have been null", bleh); + } + } + + @Override + protected void tearDown() + { + if(oldScavengeValue != null) + { + System.setProperty(SCAVENGE_PROP, oldScavengeValue); + } + else + { + System.clearProperty(SCAVENGE_PROP); + } + } + + @Override + public QueueEntryList getTestList() + { + return getTestList(false); + } + + @Override + public QueueEntryList getTestList(boolean newList) + { + if(newList) + { + return new SimpleQueueEntryList(_testQueue); + } + else + { + return _sqel; + } + } + + @Override + public long getExpectedFirstMsgId() + { + return 1; + } + + @Override + public int getExpectedListLength() + { + return 100; + } + + @Override + public ServerMessage getTestMessageToAdd() throws AMQException + { + ServerMessage msg = mock(ServerMessage.class); + when(msg.getMessageNumber()).thenReturn(1l); + return msg; + } + + public void testScavenge() throws Exception + { + SimpleQueueEntryList sqel = new SimpleQueueEntryList(null); + ConcurrentHashMap entriesMap = new ConcurrentHashMap(); + + + //Add messages to generate QueueEntry's + for(int i = 1; i <= 100 ; i++) + { + ServerMessage message = mock(ServerMessage.class); + when(message.getMessageNumber()).thenReturn((long) i); + MessageReference ref = mock(MessageReference.class); + when(ref.getMessage()).thenReturn(message); + when(message.newReference()).thenReturn(ref); + QueueEntry bleh = sqel.add(message); + assertNotNull("QE should not have been null", bleh); + entriesMap.put(i,bleh); + } + + SimpleQueueEntryImpl head = sqel.getHead(); + + //We shall now delete some specific messages mid-queue that will lead to + //requiring a scavenge once the requested threshold of 9 deletes is passed + + //Delete the 2nd message only + assertTrue("Failed to delete QueueEntry", entriesMap.remove(2).delete()); + verifyDeletedButPresentBeforeScavenge(head, 2); + + //Delete messages 12 to 14 + assertTrue("Failed to delete QueueEntry", entriesMap.remove(12).delete()); + verifyDeletedButPresentBeforeScavenge(head, 12); + assertTrue("Failed to delete QueueEntry", entriesMap.remove(13).delete()); + verifyDeletedButPresentBeforeScavenge(head, 13); + assertTrue("Failed to delete QueueEntry", entriesMap.remove(14).delete()); + verifyDeletedButPresentBeforeScavenge(head, 14); + + //Delete message 20 only + assertTrue("Failed to delete QueueEntry", entriesMap.remove(20).delete()); + verifyDeletedButPresentBeforeScavenge(head, 20); + + //Delete messages 81 to 84 + assertTrue("Failed to delete QueueEntry", entriesMap.remove(81).delete()); + verifyDeletedButPresentBeforeScavenge(head, 81); + assertTrue("Failed to delete QueueEntry", entriesMap.remove(82).delete()); + verifyDeletedButPresentBeforeScavenge(head, 82); + assertTrue("Failed to delete QueueEntry", entriesMap.remove(83).delete()); + verifyDeletedButPresentBeforeScavenge(head, 83); + assertTrue("Failed to delete QueueEntry", entriesMap.remove(84).delete()); + verifyDeletedButPresentBeforeScavenge(head, 84); + + //Delete message 99 - this is the 10th message deleted that is after the queue head + //and so will invoke the scavenge() which is set to go after 9 previous deletions + assertTrue("Failed to delete QueueEntry", entriesMap.remove(99).delete()); + + verifyAllDeletedMessagedNotPresent(head, entriesMap); + } + + private void verifyDeletedButPresentBeforeScavenge(SimpleQueueEntryImpl head, long messageId) + { + //Use the head to get the initial entry in the queue + SimpleQueueEntryImpl entry = head.getNextNode(); + + for(long i = 1; i < messageId ; i++) + { + assertEquals("Expected QueueEntry was not found in the list", i, (long) entry.getMessage().getMessageNumber()); + entry = entry.getNextNode(); + } + + assertTrue("Entry should have been deleted", entry.isDeleted()); + } + + private void verifyAllDeletedMessagedNotPresent(SimpleQueueEntryImpl head, Map remainingMessages) + { + //Use the head to get the initial entry in the queue + SimpleQueueEntryImpl entry = head.getNextNode(); + + assertNotNull("Initial entry should not have been null", entry); + + int count = 0; + + while (entry != null) + { + assertFalse("Entry " + entry.getMessage().getMessageNumber() + " should not have been deleted", entry.isDeleted()); + assertNotNull("QueueEntry "+entry.getMessage().getMessageNumber()+" was not found in the list of remaining entries " + remainingMessages, + remainingMessages.get((int)(entry.getMessage().getMessageNumber()))); + + count++; + entry = entry.getNextNode(); + } + + assertEquals("Count should have been equal",count,remainingMessages.size()); + } + + public void testGettingNextElement() + { + final int numberOfEntries = 5; + final SimpleQueueEntryImpl[] entries = new SimpleQueueEntryImpl[numberOfEntries]; + final SimpleQueueEntryList queueEntryList = new SimpleQueueEntryList(new MockAMQQueue("test")); + + // create test entries + for(int i = 0; i < numberOfEntries; i++) + { + ServerMessage message = mock(ServerMessage.class); + when(message.getMessageNumber()).thenReturn((long)i); + entries[i] = queueEntryList.add(message); + } + + // test getNext for not acquired entries + for(int i = 0; i < numberOfEntries; i++) + { + final SimpleQueueEntryImpl next = entries[i].getNextValidEntry(); + + if(i < numberOfEntries - 1) + { + assertEquals("Unexpected entry from QueueEntryImpl#getNext()", entries[i + 1], next); + } + else + { + assertNull("The next entry after the last should be null", next); + } + } + + // delete second + entries[1].acquire(); + entries[1].delete(); + + // dequeue third + entries[2].acquire(); + entries[2].dequeue(); + + SimpleQueueEntryImpl next = entries[2].getNextValidEntry(); + assertEquals("expected forth entry", entries[3], next); + next = next.getNextValidEntry(); + assertEquals("expected fifth entry", entries[4], next); + next = next.getNextValidEntry(); + assertNull("The next entry after the last should be null", next); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SortedQueueEntryImplTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SortedQueueEntryImplTest.java new file mode 100644 index 0000000000..acd0ccbdeb --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SortedQueueEntryImplTest.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.queue; + +import java.util.Collections; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.ServerMessage; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SortedQueueEntryImplTest extends QueueEntryImplTestBase { + + public final static String keys[] = { "CCC", "AAA", "BBB" }; + + private SelfValidatingSortedQueueEntryList queueEntryList = new SelfValidatingSortedQueueEntryList(new MockAMQQueue("test"),"KEY"); + + public QueueEntryImpl getQueueEntryImpl(int msgId) throws AMQException { + final ServerMessage message = mock(ServerMessage.class); + AMQMessageHeader hdr = mock(AMQMessageHeader.class); + when(message.getMessageHeader()).thenReturn(hdr); + when(hdr.getHeader(eq("KEY"))).thenReturn(keys[msgId-1]); + when(hdr.containsHeader(eq("KEY"))).thenReturn(true); + when(hdr.getHeaderNames()).thenReturn(Collections.singleton("KEY")); + + return queueEntryList.add(message); + } + + public void testCompareTo() + { + assertTrue(_queueEntry.compareTo(_queueEntry2) > 0); + assertTrue(_queueEntry.compareTo(_queueEntry3) > 0); + + assertTrue(_queueEntry2.compareTo(_queueEntry3) < 0); + assertTrue(_queueEntry2.compareTo(_queueEntry) < 0); + + assertTrue(_queueEntry3.compareTo(_queueEntry2) > 0); + assertTrue(_queueEntry3.compareTo(_queueEntry) < 0); + + assertTrue(_queueEntry.compareTo(_queueEntry) == 0); + assertTrue(_queueEntry2.compareTo(_queueEntry2) == 0); + assertTrue(_queueEntry3.compareTo(_queueEntry3) == 0); + } + + public void testTraverseWithNoDeletedEntries() + { + QueueEntry current = _queueEntry2; + + current = current.getNextValidEntry(); + assertSame("Unexpected current entry",_queueEntry3, current); + + current = current.getNextValidEntry(); + assertSame("Unexpected current entry",_queueEntry, current); + + current = current.getNextValidEntry(); + assertNull(current); + + } + + public void testTraverseWithDeletedEntries() + { + // Delete 2nd queue entry + _queueEntry3.delete(); + assertTrue(_queueEntry3.isDeleted()); + + QueueEntry current = _queueEntry2; + + current = current.getNextValidEntry(); + assertSame("Unexpected current entry",_queueEntry, current); + + current = current.getNextValidEntry(); + assertNull(current); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SortedQueueEntryListTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SortedQueueEntryListTest.java new file mode 100644 index 0000000000..7add2d4d43 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/queue/SortedQueueEntryListTest.java @@ -0,0 +1,373 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.Collections; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; + +import java.util.Arrays; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SortedQueueEntryListTest extends QueueEntryListTestBase +{ + private static SelfValidatingSortedQueueEntryList _sqel; + + public final static String keys[] = { " 73", " 18", " 11", "127", "166", "163", " 69", " 60", "191", "144", + " 17", "161", "145", "140", "157", " 47", "136", " 56", "176", " 81", + "195", " 96", " 2", " 68", "101", "141", "159", "187", "149", " 45", + " 64", "100", " 83", " 51", " 79", " 82", "180", " 26", " 61", " 62", + " 78", " 46", "147", " 91", "120", "164", " 92", "172", "188", " 50", + "111", " 89", " 4", " 8", " 16", "151", "122", "178", " 33", "124", + "171", "165", "116", "113", "155", "148", " 29", " 0", " 37", "131", + "146", " 57", "112", " 97", " 23", "108", "123", "117", "167", " 52", + " 98", " 6", "160", " 25", " 49", " 34", "182", "185", " 30", " 66", + "152", " 58", " 86", "118", "189", " 84", " 36", "104", " 7", " 76", + " 87", " 1", " 80", " 10", "142", " 59", "137", " 12", " 67", " 22", + " 9", "106", " 75", "109", " 93", " 42", "177", "134", " 77", " 88", + "114", " 43", "143", "135", " 55", "181", " 32", "174", "175", "184", + "133", "107", " 28", "126", "103", " 85", " 38", "158", " 39", "162", + "129", "194", " 15", " 24", " 19", " 35", "186", " 31", " 65", " 99", + "192", " 74", "156", " 27", " 95", " 54", " 70", " 13", "110", " 41", + " 90", "173", "125", "196", "130", "183", "102", "190", "132", "105", + " 21", " 53", "139", " 94", "115", " 48", " 44", "179", "128", " 14", + " 72", "119", "153", "168", "197", " 40", "150", "138", " 5", "154", + "169", " 71", "199", "198", "170", " 3", "121", " 20", " 63", "193" }; + + public final static String textkeys[] = { "AAA", "BBB", "CCC", "DDD", "EEE", "FFF", "GGG", "HHH", "III", "JJJ", + "KKK", "LLL", "MMM", "NNN", "OOO", "PPP", "QQQ", "RRR", "SSS", "TTT", + "UUU", "VVV", "XXX", "YYY", "ZZZ"}; + + private final static String keysSorted[] = keys.clone(); + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + // Create result array + Arrays.sort(keysSorted); + + // Create test list + _sqel = new SelfValidatingSortedQueueEntryList(_testQueue, "KEY"); + + // Build test list + long messageId = 0L; + for(final String key : keys) + { + final ServerMessage msg = generateTestMessage(messageId++, key); + _sqel.add(msg); + } + + } + + @Override + public QueueEntryList getTestList() + { + return getTestList(false); + } + + @Override + public QueueEntryList getTestList(boolean newList) + { + if(newList) + { + return new SelfValidatingSortedQueueEntryList(_testQueue, "KEY"); + } + else + { + return _sqel; + } + } + + public int getExpectedListLength() + { + return keys.length; + } + + public long getExpectedFirstMsgId() + { + return 67L; + } + + public ServerMessage getTestMessageToAdd() throws AMQException + { + return generateTestMessage(1, "test value"); + } + + private ServerMessage generateTestMessage(final long id, final String keyValue) throws AMQException + { + final ServerMessage message = mock(ServerMessage.class); + AMQMessageHeader hdr = mock(AMQMessageHeader.class); + when(message.getMessageHeader()).thenReturn(hdr); + when(hdr.getHeader(eq("KEY"))).thenReturn(keyValue); + when(hdr.containsHeader(eq("KEY"))).thenReturn(true); + when(hdr.getHeaderNames()).thenReturn(Collections.singleton("KEY")); + MessageReference ref = mock(MessageReference.class); + when(ref.getMessage()).thenReturn(message); + when(message.newReference()).thenReturn(ref); + when(message.getMessageNumber()).thenReturn(id); + + return message; + } + + public void testIterator() + { + super.testIterator(); + + // Test sorted order of list + final QueueEntryIterator iter = getTestList().iterator(); + int count = 0; + while(iter.advance()) + { + assertEquals("Sorted queue entry value does not match sorted key array", + keysSorted[count++], getSortedKeyValue(iter)); + } + } + + private Object getSortedKeyValue(QueueEntryIterator iter) + { + return (iter.getNode()).getMessage().getMessageHeader().getHeader("KEY"); + } + + private Long getMessageId(QueueEntryIterator iter) + { + return (iter.getNode()).getMessage().getMessageNumber(); + } + + public void testNonUniqueSortKeys() throws Exception + { + _sqel = new SelfValidatingSortedQueueEntryList(_testQueue, "KEY"); + + // Build test list + long messageId = 0L; + while(messageId < 200) + { + final ServerMessage msg = generateTestMessage(messageId++, "samekey"); + _sqel.add(msg); + } + + final QueueEntryIterator iter = getTestList().iterator(); + int count=0; + while(iter.advance()) + { + assertEquals("Sorted queue entry value is not as expected", "samekey", getSortedKeyValue(iter)); + assertEquals("Message id not as expected", Long.valueOf(count++), getMessageId(iter)); + } + } + + public void testNullSortKeys() throws Exception + { + _sqel = new SelfValidatingSortedQueueEntryList(_testQueue, "KEY"); + + // Build test list + long messageId = 0L; + while(messageId < 200) + { + final ServerMessage msg = generateTestMessage(messageId++, null); + _sqel.add(msg); + } + + final QueueEntryIterator iter = getTestList().iterator(); + int count=0; + while(iter.advance()) + { + assertNull("Sorted queue entry value is not as expected", getSortedKeyValue(iter)); + assertEquals("Message id not as expected", Long.valueOf(count++), getMessageId(iter)); } + } + + public void testAscendingSortKeys() throws Exception + { + _sqel = new SelfValidatingSortedQueueEntryList(_testQueue, "KEY"); + + // Build test list + long messageId = 0L; + for(String textKey : textkeys) + { + final ServerMessage msg = generateTestMessage(messageId, textKey); + messageId++; + _sqel.add(msg); + } + + final QueueEntryIterator iter = getTestList().iterator(); + int count=0; + while(iter.advance()) + { + assertEquals("Sorted queue entry value is not as expected", textkeys[count], getSortedKeyValue(iter)); + assertEquals("Message id not as expected", Long.valueOf(count), getMessageId(iter)); + count++; + } + } + + public void testDescendingSortKeys() throws Exception + { + _sqel = new SelfValidatingSortedQueueEntryList(_testQueue, "KEY"); + + // Build test list + long messageId = 0L; + for(int i=textkeys.length-1; i >=0; i--) + { + final ServerMessage msg = generateTestMessage(messageId, textkeys[i]); + messageId++; + _sqel.add(msg); + } + + final QueueEntryIterator iter = getTestList().iterator(); + int count=0; + while(iter.advance()) + { + assertEquals("Sorted queue entry value is not as expected", textkeys[count], getSortedKeyValue(iter)); + assertEquals("Message id not as expected", Long.valueOf(textkeys.length-count-1), getMessageId(iter)); + count++; + } + } + + public void testInsertAfter() throws Exception + { + _sqel = new SelfValidatingSortedQueueEntryList(_testQueue, "KEY"); + + ServerMessage msg = generateTestMessage(1, "A"); + _sqel.add(msg); + + SortedQueueEntryImpl entry = _sqel.next(_sqel.getHead()); + validateEntry(entry, "A", 1); + + msg = generateTestMessage(2, "B"); + _sqel.add(msg); + + entry = _sqel.next(_sqel.getHead()); + validateEntry(entry, "A", 1); + + entry = _sqel.next(entry); + validateEntry(entry, "B", 2); + } + + public void testInsertBefore() throws Exception + { + _sqel = new SelfValidatingSortedQueueEntryList(_testQueue, "KEY"); + + ServerMessage msg = generateTestMessage(1, "B"); + _sqel.add(msg); + + SortedQueueEntryImpl entry = _sqel.next(_sqel.getHead()); + validateEntry(entry, "B", 1); + + msg = generateTestMessage(2, "A"); + _sqel.add(msg); + + entry = _sqel.next(_sqel.getHead()); + validateEntry(entry, "A", 2); + + entry = _sqel.next(entry); + validateEntry(entry, "B", 1); + } + + public void testInsertInbetween() throws Exception + { + _sqel = new SelfValidatingSortedQueueEntryList(_testQueue, "KEY"); + + ServerMessage msg = generateTestMessage(1, "A"); + _sqel.add(msg); + SortedQueueEntryImpl entry = _sqel.next(_sqel.getHead()); + validateEntry(entry, "A", 1); + + msg = generateTestMessage(2, "C"); + _sqel.add(msg); + + entry = _sqel.next(_sqel.getHead()); + validateEntry(entry, "A", 1); + + entry = _sqel.next(entry); + validateEntry(entry, "C", 2); + + msg = generateTestMessage(3, "B"); + _sqel.add(msg); + + entry = _sqel.next(_sqel.getHead()); + validateEntry(entry, "A", 1); + + entry = _sqel.next(entry); + validateEntry(entry, "B", 3); + + entry = _sqel.next(entry); + validateEntry(entry, "C", 2); + } + + public void testInsertAtHead() throws Exception + { + _sqel = new SelfValidatingSortedQueueEntryList(_testQueue, "KEY"); + + ServerMessage msg = generateTestMessage(1, "B"); + _sqel.add(msg); + + SortedQueueEntryImpl entry = _sqel.next(_sqel.getHead()); + validateEntry(entry, "B", 1); + + msg = generateTestMessage(2, "D"); + _sqel.add(msg); + + entry = _sqel.next(_sqel.getHead()); + validateEntry(entry, "B", 1); + + entry = _sqel.next(entry); + validateEntry(entry, "D", 2); + + msg = generateTestMessage(3, "C"); + _sqel.add(msg); + + entry = _sqel.next(_sqel.getHead()); + validateEntry(entry, "B", 1); + + entry = _sqel.next(entry); + validateEntry(entry, "C", 3); + + entry = _sqel.next(entry); + validateEntry(entry, "D", 2); + + msg = generateTestMessage(4, "A"); + _sqel.add(msg); + + entry = _sqel.next(_sqel.getHead()); + validateEntry(entry, "A", 4); + + entry = _sqel.next(entry); + validateEntry(entry, "B", 1); + + entry = _sqel.next(entry); + validateEntry(entry, "C", 3); + + entry = _sqel.next(entry); + validateEntry(entry, "D", 2); + } + + private void validateEntry(final SortedQueueEntryImpl entry, final String expectedSortKey, final long expectedMessageId) + { + assertEquals("Sorted queue entry value is not as expected", + expectedSortKey, entry.getMessage().getMessageHeader().getHeader("KEY")); + assertEquals("Sorted queue entry id is not as expected", + expectedMessageId, entry.getMessage().getMessageNumber()); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/SubjectCreatorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/SubjectCreatorTest.java new file mode 100644 index 0000000000..9edd345360 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/SubjectCreatorTest.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.security; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.sasl.SaslServer; + +import junit.framework.TestCase; + +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; + +public class SubjectCreatorTest extends TestCase +{ + private static final String USERNAME = "username"; + private static final String PASSWORD = "password"; + + private AuthenticationManager _authenticationManager = mock(AuthenticationManager.class); + + private GroupProvider _groupManager1 = mock(GroupProvider.class); + private GroupProvider _groupManager2 = mock(GroupProvider.class); + + private Principal _userPrincipal = mock(Principal.class); + private Principal _group1 = mock(Principal.class); + private Principal _group2 = mock(Principal.class); + + private SubjectCreator _subjectCreator; + private AuthenticationResult _authenticationResult; + private SaslServer _testSaslServer = mock(SaslServer.class); + private byte[] _saslResponseBytes = PASSWORD.getBytes(); + + @Override + public void setUp() + { + when(_groupManager1.getGroupPrincipalsForUser(USERNAME)).thenReturn(Collections.singleton(_group1)); + when(_groupManager2.getGroupPrincipalsForUser(USERNAME)).thenReturn(Collections.singleton(_group2)); + + _subjectCreator = new SubjectCreator(_authenticationManager, new HashSet(Arrays.asList(_groupManager1, _groupManager2))); + _authenticationResult = new AuthenticationResult(_userPrincipal); + when(_authenticationManager.authenticate(USERNAME, PASSWORD)).thenReturn(_authenticationResult); + } + + public void testAuthenticateUsernameAndPasswordReturnsSubjectWithUserAndGroupPrincipals() + { + final SubjectAuthenticationResult actualResult = _subjectCreator.authenticate(USERNAME, PASSWORD); + + assertEquals(AuthenticationStatus.SUCCESS, actualResult.getStatus()); + + final Subject actualSubject = actualResult.getSubject(); + + assertEquals("Should contain one user principal and two groups ", 3, actualSubject.getPrincipals().size()); + + assertTrue(actualSubject.getPrincipals().contains(new AuthenticatedPrincipal(_userPrincipal))); + assertTrue(actualSubject.getPrincipals().contains(_group1)); + assertTrue(actualSubject.getPrincipals().contains(_group2)); + + assertTrue(actualSubject.isReadOnly()); + } + + public void testSaslAuthenticationSuccessReturnsSubjectWithUserAndGroupPrincipals() throws Exception + { + when(_authenticationManager.authenticate(_testSaslServer, _saslResponseBytes)).thenReturn(_authenticationResult); + when(_testSaslServer.isComplete()).thenReturn(true); + when(_testSaslServer.getAuthorizationID()).thenReturn(USERNAME); + + SubjectAuthenticationResult result = _subjectCreator.authenticate(_testSaslServer, _saslResponseBytes); + + final Subject actualSubject = result.getSubject(); + assertEquals("Should contain one user principal and two groups ", 3, actualSubject.getPrincipals().size()); + + assertTrue(actualSubject.getPrincipals().contains(new AuthenticatedPrincipal(_userPrincipal))); + assertTrue(actualSubject.getPrincipals().contains(_group1)); + assertTrue(actualSubject.getPrincipals().contains(_group2)); + + assertTrue(actualSubject.isReadOnly()); + } + + public void testAuthenticateUnsuccessfulWithUsernameReturnsNullSubjectAndCorrectStatus() + { + testUnsuccessfulAuthentication(AuthenticationResult.AuthenticationStatus.CONTINUE); + testUnsuccessfulAuthentication(AuthenticationResult.AuthenticationStatus.ERROR); + } + + private void testUnsuccessfulAuthentication(AuthenticationStatus expectedStatus) + { + AuthenticationResult failedAuthenticationResult = new AuthenticationResult(expectedStatus); + + when(_authenticationManager.authenticate(USERNAME, PASSWORD)).thenReturn(failedAuthenticationResult); + + SubjectAuthenticationResult subjectAuthenticationResult = _subjectCreator.authenticate(USERNAME, PASSWORD); + + assertSame(expectedStatus, subjectAuthenticationResult.getStatus()); + assertNull(subjectAuthenticationResult.getSubject()); + } + + public void testAuthenticateUnsuccessfulWithSaslServerReturnsNullSubjectAndCorrectStatus() + { + testUnsuccessfulAuthenticationWithSaslServer(AuthenticationResult.AuthenticationStatus.CONTINUE); + testUnsuccessfulAuthenticationWithSaslServer(AuthenticationResult.AuthenticationStatus.ERROR); + } + + private void testUnsuccessfulAuthenticationWithSaslServer(AuthenticationStatus expectedStatus) + { + AuthenticationResult failedAuthenticationResult = new AuthenticationResult(expectedStatus); + + when(_authenticationManager.authenticate(_testSaslServer, _saslResponseBytes)).thenReturn(failedAuthenticationResult); + when(_testSaslServer.isComplete()).thenReturn(false); + + SubjectAuthenticationResult subjectAuthenticationResult = _subjectCreator.authenticate(_testSaslServer, _saslResponseBytes); + + assertSame(expectedStatus, subjectAuthenticationResult.getStatus()); + assertNull(subjectAuthenticationResult.getSubject()); + } + + public void testGetGroupPrincipals() + { + getAndAssertGroupPrincipals(_group1, _group2); + } + + public void testGetGroupPrincipalsWhenAGroupManagerReturnsNull() + { + when(_groupManager1.getGroupPrincipalsForUser(USERNAME)).thenReturn(null); + + getAndAssertGroupPrincipals(_group2); + } + + public void testGetGroupPrincipalsWhenAGroupManagerReturnsEmptySet() + { + when(_groupManager2.getGroupPrincipalsForUser(USERNAME)).thenReturn(new HashSet()); + + getAndAssertGroupPrincipals(_group1); + } + + private void getAndAssertGroupPrincipals(Principal... expectedGroups) + { + Set actualGroupPrincipals = _subjectCreator.getGroupPrincipals(USERNAME); + Set expectedGroupPrincipals = new HashSet(Arrays.asList(expectedGroups)); + assertEquals(expectedGroupPrincipals, actualGroupPrincipals); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTest.java new file mode 100644 index 0000000000..cd5791952f --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTest.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.security.auth; + +import java.security.Principal; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.auth.UsernamePrincipal; + +import junit.framework.TestCase; + +public class AuthenticatedPrincipalTest extends TestCase +{ + + private AuthenticatedPrincipal _authenticatedPrincipal = new AuthenticatedPrincipal(new UsernamePrincipal("name")); + + public void testGetAuthenticatedPrincipalFromSubject() + { + final Subject subject = createSubjectContainingAuthenticatedPrincipal(); + final AuthenticatedPrincipal actual = AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(subject); + assertSame(_authenticatedPrincipal, actual); + } + + public void testAuthenticatedPrincipalNotInSubject() + { + try + { + AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(new Subject()); + fail("Exception not thrown"); + } + catch (IllegalArgumentException iae) + { + // PASS + } + } + + public void testGetOptionalAuthenticatedPrincipalFromSubject() + { + final Subject subject = createSubjectContainingAuthenticatedPrincipal(); + final AuthenticatedPrincipal actual = AuthenticatedPrincipal.getOptionalAuthenticatedPrincipalFromSubject(subject); + assertSame(_authenticatedPrincipal, actual); + } + + public void testGetOptionalAuthenticatedPrincipalFromSubjectReturnsNullIfMissing() + { + Subject subjectWithNoPrincipals = new Subject(); + assertNull(AuthenticatedPrincipal.getOptionalAuthenticatedPrincipalFromSubject(subjectWithNoPrincipals)); + + Subject subjectWithoutAuthenticatedPrincipal = new Subject(); + subjectWithoutAuthenticatedPrincipal.getPrincipals().add(new UsernamePrincipal("name1")); + assertNull("Should return null for a subject containing a principal that isn't an AuthenticatedPrincipal", + AuthenticatedPrincipal.getOptionalAuthenticatedPrincipalFromSubject(subjectWithoutAuthenticatedPrincipal)); + } + + public void testTooManyAuthenticatedPrincipalsInSubject() + { + final Subject subject = new Subject(); + subject.getPrincipals().add(new AuthenticatedPrincipal(new UsernamePrincipal("name1"))); + subject.getPrincipals().add(new AuthenticatedPrincipal(new UsernamePrincipal("name2"))); + + try + { + AuthenticatedPrincipal.getAuthenticatedPrincipalFromSubject(subject); + fail("Exception not thrown"); + } + catch (IllegalArgumentException iae) + { + // PASS + } + } + + private Subject createSubjectContainingAuthenticatedPrincipal() + { + final Principal other = new Principal() + { + public String getName() + { + return "otherprincipal"; + } + }; + + final Subject subject = new Subject(); + subject.getPrincipals().add(_authenticatedPrincipal); + subject.getPrincipals().add(other); + return subject; + } + + public void testEqualsAndHashcode() + { + AuthenticatedPrincipal user1principal1 = new AuthenticatedPrincipal(new UsernamePrincipal("user1")); + AuthenticatedPrincipal user1principal2 = new AuthenticatedPrincipal(new UsernamePrincipal("user1")); + + assertTrue(user1principal1.equals(user1principal1)); + assertTrue(user1principal1.equals(user1principal2)); + assertTrue(user1principal2.equals(user1principal1)); + + assertEquals(user1principal1.hashCode(), user1principal2.hashCode()); + } + + public void testEqualsAndHashcodeWithSameWrappedObject() + { + UsernamePrincipal wrappedPrincipal = new UsernamePrincipal("user1"); + AuthenticatedPrincipal user1principal1 = new AuthenticatedPrincipal(wrappedPrincipal); + AuthenticatedPrincipal user1principal2 = new AuthenticatedPrincipal(wrappedPrincipal); + + assertTrue(user1principal1.equals(user1principal1)); + assertTrue(user1principal1.equals(user1principal2)); + assertTrue(user1principal2.equals(user1principal1)); + + assertEquals(user1principal1.hashCode(), user1principal2.hashCode()); + } + + public void testEqualsWithDifferentUsernames() + { + AuthenticatedPrincipal user1principal1 = new AuthenticatedPrincipal(new UsernamePrincipal("user1")); + AuthenticatedPrincipal user1principal2 = new AuthenticatedPrincipal(new UsernamePrincipal("user2")); + + assertFalse(user1principal1.equals(user1principal2)); + assertFalse(user1principal2.equals(user1principal1)); + } + + public void testEqualsWithDisimilarObjects() + { + UsernamePrincipal wrappedPrincipal = new UsernamePrincipal("user1"); + AuthenticatedPrincipal authenticatedPrincipal = new AuthenticatedPrincipal(wrappedPrincipal); + + assertFalse(authenticatedPrincipal.equals(wrappedPrincipal)); + assertFalse(wrappedPrincipal.equals(authenticatedPrincipal)); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTestHelper.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTestHelper.java new file mode 100644 index 0000000000..e9d8d16fce --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticatedPrincipalTestHelper.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.security.auth; + +import java.security.Principal; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.Assert; + +/** + * Helper class for testing that sets of principals contain {@link AuthenticatedPrincipal}'s that wrap + * expected {@link Principal}'s. + */ +public class AuthenticatedPrincipalTestHelper +{ + public static void assertOnlyContainsWrapped(Principal wrappedPrincipal, Set principals) + { + assertOnlyContainsWrappedAndSecondaryPrincipals(wrappedPrincipal, Collections.emptySet(), principals); + } + + + public static void assertOnlyContainsWrappedAndSecondaryPrincipals( + Principal expectedWrappedPrincipal, + Set expectedSecondaryPrincipals, + Set actualPrincipals) + { + Assert.assertEquals("Principal set should contain one principal " + "but the principal set is: " + actualPrincipals, + 1 + expectedSecondaryPrincipals.size(), + actualPrincipals.size()); + + Set expectedSet = new HashSet(expectedSecondaryPrincipals); + expectedSet.add(new AuthenticatedPrincipal(expectedWrappedPrincipal)); + + Assert.assertEquals(expectedSet, actualPrincipals); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticationResultTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticationResultTest.java new file mode 100644 index 0000000000..a023cbdbb2 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/AuthenticationResultTest.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.security.auth; + +import static org.apache.qpid.server.security.auth.AuthenticatedPrincipalTestHelper.assertOnlyContainsWrapped; +import static org.apache.qpid.server.security.auth.AuthenticatedPrincipalTestHelper.assertOnlyContainsWrappedAndSecondaryPrincipals; +import static org.mockito.Mockito.mock; + +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; + +public class AuthenticationResultTest extends TestCase +{ + public void testConstructWithAuthenticationStatusContinue() + { + AuthenticationResult authenticationResult = new AuthenticationResult(AuthenticationResult.AuthenticationStatus.CONTINUE); + assertSame(AuthenticationResult.AuthenticationStatus.CONTINUE, authenticationResult.getStatus()); + assertTrue(authenticationResult.getPrincipals().isEmpty()); + } + + public void testConstructWithAuthenticationStatusError() + { + AuthenticationResult authenticationResult = new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR); + assertSame(AuthenticationResult.AuthenticationStatus.ERROR, authenticationResult.getStatus()); + assertTrue(authenticationResult.getPrincipals().isEmpty()); + } + + public void testConstructWithAuthenticationStatusSuccessThrowsException() + { + try + { + new AuthenticationResult(AuthenticationResult.AuthenticationStatus.SUCCESS); + fail("Exception not thrown"); + } + catch(IllegalArgumentException e) + { + // PASS + } + } + + public void testConstructWithPrincipal() + { + Principal mainPrincipal = mock(Principal.class); + AuthenticationResult authenticationResult = new AuthenticationResult(mainPrincipal); + + assertOnlyContainsWrapped(mainPrincipal, authenticationResult.getPrincipals()); + assertSame(AuthenticationResult.AuthenticationStatus.SUCCESS, authenticationResult.getStatus()); + } + + public void testConstructWithNullPrincipalThrowsException() + { + try + { + new AuthenticationResult((Principal)null); + fail("Exception not thrown"); + } + catch(IllegalArgumentException e) + { + // pass + } + } + + public void testConstructWithSetOfPrincipals() + { + Principal mainPrincipal = mock(Principal.class); + Principal secondaryPrincipal = mock(Principal.class); + Set secondaryPrincipals = Collections.singleton(secondaryPrincipal); + + AuthenticationResult authenticationResult = new AuthenticationResult(mainPrincipal, secondaryPrincipals); + + assertOnlyContainsWrappedAndSecondaryPrincipals(mainPrincipal, secondaryPrincipals, authenticationResult.getPrincipals()); + assertSame(AuthenticationResult.AuthenticationStatus.SUCCESS, authenticationResult.getStatus()); + } + + public void testConstructWithSetOfPrincipalsDeDuplicatesMainPrincipal() + { + Principal mainPrincipal = mock(Principal.class); + Principal secondaryPrincipal = mock(Principal.class); + + Set secondaryPrincipalsContainingDuplicateOfMainPrincipal = new HashSet( + Arrays.asList(secondaryPrincipal, mainPrincipal)); + Set deDuplicatedSecondaryPrincipals = Collections.singleton(secondaryPrincipal); + + AuthenticationResult authenticationResult = new AuthenticationResult( + mainPrincipal, secondaryPrincipalsContainingDuplicateOfMainPrincipal); + + assertOnlyContainsWrappedAndSecondaryPrincipals(mainPrincipal, deDuplicatedSecondaryPrincipals, authenticationResult.getPrincipals()); + + assertSame(AuthenticationResult.AuthenticationStatus.SUCCESS, authenticationResult.getStatus()); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/TestPrincipalUtils.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/TestPrincipalUtils.java new file mode 100644 index 0000000000..ea6b40e3de --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/TestPrincipalUtils.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth; + +import javax.security.auth.Subject; + +import org.apache.qpid.server.security.group.GroupPrincipal; + +import java.security.Principal; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class TestPrincipalUtils +{ + /** + * Creates a test subject, with exactly one {@link AuthenticatedPrincipal} and zero or more GroupPrincipals. + */ + public static Subject createTestSubject(final String username, final String... groups) + { + final Set principals = new HashSet(1 + groups.length); + principals.add(new AuthenticatedPrincipal(username)); + for (String group : groups) + { + principals.add(new GroupPrincipal(group)); + } + + return new Subject(true, principals, Collections.EMPTY_SET, Collections.EMPTY_SET); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/UsernamePrincipalTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/UsernamePrincipalTest.java new file mode 100644 index 0000000000..5e025d3ca8 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/UsernamePrincipalTest.java @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth; + +import junit.framework.TestCase; + +/** + * Tests the UsernamePrincipal. + * + */ +public class UsernamePrincipalTest extends TestCase +{ + public void testEqualitySameObject() + { + final UsernamePrincipal principal = new UsernamePrincipal("string"); + assertTrue(principal.equals(principal)); + } + + public void testEqualitySameName() + { + final String string = "string"; + final UsernamePrincipal principal1 = new UsernamePrincipal(string); + final UsernamePrincipal principal2 = new UsernamePrincipal(string); + assertTrue(principal1.equals(principal2)); + } + + public void testEqualityEqualName() + { + final UsernamePrincipal principal1 = new UsernamePrincipal(new String("string")); + final UsernamePrincipal principal2 = new UsernamePrincipal(new String("string")); + assertTrue(principal1.equals(principal2)); + } + + public void testInequalityDifferentUserPrincipals() + { + UsernamePrincipal principal1 = new UsernamePrincipal("string1"); + UsernamePrincipal principal2 = new UsernamePrincipal("string2"); + assertFalse(principal1.equals(principal2)); + } + + public void testInequalityNonUserPrincipal() + { + UsernamePrincipal principal = new UsernamePrincipal("string"); + assertFalse(principal.equals(new String("string"))); + } + + public void testInequalityNull() + { + UsernamePrincipal principal = new UsernamePrincipal("string"); + assertFalse(principal.equals(null)); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java new file mode 100644 index 0000000000..4102a1fc68 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabaseTest.java @@ -0,0 +1,465 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.database; + +import junit.framework.TestCase; +import org.apache.commons.codec.binary.Base64; + +import org.apache.qpid.server.security.auth.UsernamePrincipal; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public class Base64MD5PasswordFilePrincipalDatabaseTest extends TestCase +{ + + private static final String TEST_COMMENT = "# Test Comment"; + + private static final String USERNAME = "testUser"; + private static final String PASSWORD = "guest"; + private static final String PASSWORD_B64MD5HASHED = "CE4DQ6BIb/BVMN9scFyLtA=="; + private static char[] PASSWORD_MD5_CHARS; + private static final String PRINCIPAL_USERNAME = "testUserPrincipal"; + private static final Principal PRINCIPAL = new UsernamePrincipal(PRINCIPAL_USERNAME); + private Base64MD5PasswordFilePrincipalDatabase _database; + private File _pwdFile; + private List _testPwdFiles = new ArrayList(); + + static + { + try + { + Base64 b64 = new Base64(); + byte[] md5passBytes = PASSWORD_B64MD5HASHED.getBytes(Base64MD5PasswordFilePrincipalDatabase.DEFAULT_ENCODING); + byte[] decoded = b64.decode(md5passBytes); + + PASSWORD_MD5_CHARS = new char[decoded.length]; + + int index = 0; + for (byte c : decoded) + { + PASSWORD_MD5_CHARS[index++] = (char) c; + } + } + catch (UnsupportedEncodingException e) + { + fail("Unable to perform B64 decode to get the md5 char[] password"); + } + } + + + public void setUp() throws Exception + { + _database = new Base64MD5PasswordFilePrincipalDatabase(); + _pwdFile = File.createTempFile(this.getClass().getName(), "pwd"); + _pwdFile.deleteOnExit(); + _database.open(_pwdFile); + _testPwdFiles.clear(); + } + + public void tearDown() throws Exception + { + //clean up the created default password file and any backup + File oldPwdFile = new File(_pwdFile.getAbsolutePath() + ".old"); + if(oldPwdFile.exists()) + { + oldPwdFile.delete(); + } + + _pwdFile.delete(); + + //clean up any additional files and their backups + for(File f : _testPwdFiles) + { + oldPwdFile = new File(f.getAbsolutePath() + ".old"); + if(oldPwdFile.exists()) + { + oldPwdFile.delete(); + } + + f.delete(); + } + } + + private File createPasswordFile(int commentLines, int users) + { + try + { + File testFile = File.createTempFile("Base64MD5PDPDTest","tmp"); + testFile.deleteOnExit(); + + BufferedWriter writer = new BufferedWriter(new FileWriter(testFile)); + + for (int i = 0; i < commentLines; i++) + { + writer.write(TEST_COMMENT); + writer.newLine(); + } + + for (int i = 0; i < users; i++) + { + writer.write(USERNAME + i + ":Password"); + writer.newLine(); + } + + writer.flush(); + writer.close(); + + _testPwdFiles.add(testFile); + + return testFile; + + } + catch (IOException e) + { + fail("Unable to create test password file." + e.getMessage()); + } + + return null; + } + + private void loadPasswordFile(File file) + { + try + { + _database.open(file); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + } + + /** **** Test Methods ************** */ + + public void testCreatePrincipal() + { + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + + Principal principal = new Principal() + { + public String getName() + { + return USERNAME; + } + }; + + assertTrue("New user not created.", _database.createPrincipal(principal, PASSWORD.toCharArray())); + + PasswordCallback callback = new PasswordCallback("prompt",false); + try + { + _database.setPassword(principal, callback); + } + catch (AccountNotFoundException e) + { + fail("user account did not exist"); + } + assertTrue("Password returned was incorrect.", Arrays.equals(PASSWORD_MD5_CHARS, callback.getPassword())); + + loadPasswordFile(testFile); + + try + { + _database.setPassword(principal, callback); + } + catch (AccountNotFoundException e) + { + fail("user account did not exist"); + } + assertTrue("Password returned was incorrect.", Arrays.equals(PASSWORD_MD5_CHARS, callback.getPassword())); + + assertNotNull("Created User was not saved", _database.getUser(USERNAME)); + + assertFalse("Duplicate user created.", _database.createPrincipal(principal, PASSWORD.toCharArray())); + } + + public void testCreatePrincipalIsSavedToFile() + { + + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + final String CREATED_PASSWORD = "guest"; + final String CREATED_B64MD5HASHED_PASSWORD = "CE4DQ6BIb/BVMN9scFyLtA=="; + final String CREATED_USERNAME = "createdUser"; + + Principal principal = new Principal() + { + public String getName() + { + return CREATED_USERNAME; + } + }; + + _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray()); + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", CREATED_USERNAME, result[0]); + assertEquals("Password not correct,", CREATED_B64MD5HASHED_PASSWORD, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + } + + + public void testDeletePrincipal() + { + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal user = _database.getUser(USERNAME + "0"); + assertNotNull("Generated user not present.", user); + + try + { + _database.deletePrincipal(user); + } + catch (AccountNotFoundException e) + { + fail("User should be present" + e.getMessage()); + } + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + loadPasswordFile(testFile); + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + assertNull("Deleted user still present.", _database.getUser(USERNAME + "0")); + } + + public void testGetUsers() + { + int USER_COUNT = 10; + File testFile = createPasswordFile(1, USER_COUNT); + + loadPasswordFile(testFile); + + Principal user = _database.getUser("MISSING_USERNAME"); + assertNull("Missing user present.", user); + + List users = _database.getUsers(); + + assertNotNull("Users list is null.", users); + + assertEquals(USER_COUNT, users.size()); + + boolean[] verify = new boolean[USER_COUNT]; + for (int i = 0; i < USER_COUNT; i++) + { + Principal principal = users.get(i); + + assertNotNull("Generated user not present.", principal); + + String name = principal.getName(); + + int id = Integer.parseInt(name.substring(USERNAME.length())); + + assertFalse("Duplicated username retrieve", verify[id]); + verify[id] = true; + } + + for (int i = 0; i < USER_COUNT; i++) + { + assertTrue("User " + i + " missing", verify[i]); + } + } + + public void testUpdatePasswordIsSavedToFile() + { + + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal testUser = _database.getUser(USERNAME + "0"); + + assertNotNull(testUser); + + String NEW_PASSWORD = "guest"; + String NEW_PASSWORD_HASH = "CE4DQ6BIb/BVMN9scFyLtA=="; + try + { + _database.updatePassword(testUser, NEW_PASSWORD.toCharArray()); + } + catch (AccountNotFoundException e) + { + fail(e.toString()); + } + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", USERNAME + "0", result[0]); + assertEquals("New Password not correct,", NEW_PASSWORD_HASH, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + } + + public void testSetPasswordFileWithMissingFile() + { + try + { + _database.open(new File("DoesntExist")); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage(), fnfe.getMessage().startsWith("Cannot find password file")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + } + + public void testSetPasswordFileWithReadOnlyFile() + { + + File testFile = createPasswordFile(0, 0); + + testFile.setReadOnly(); + + try + { + _database.open(testFile); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage().startsWith("Cannot read password file ")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + } + + public void testCreateUserPrincipal() throws IOException + { + _database.createPrincipal(PRINCIPAL, PASSWORD.toCharArray()); + Principal newPrincipal = _database.getUser(PRINCIPAL_USERNAME); + assertNotNull(newPrincipal); + assertEquals(PRINCIPAL.getName(), newPrincipal.getName()); + } + + public void testVerifyPassword() throws IOException, AccountNotFoundException + { + testCreateUserPrincipal(); + //assertFalse(_pwdDB.verifyPassword(_username, null)); + assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, new char[]{})); + assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, (PASSWORD+"z").toCharArray())); + assertTrue(_database.verifyPassword(PRINCIPAL_USERNAME, PASSWORD.toCharArray())); + + try + { + _database.verifyPassword("made.up.username", PASSWORD.toCharArray()); + fail("Should not have been able to verify this non-existant users password."); + } + catch (AccountNotFoundException e) + { + // pass + } + } + + public void testUpdatePassword() throws IOException, AccountNotFoundException + { + testCreateUserPrincipal(); + char[] newPwd = "newpassword".toCharArray(); + _database.updatePassword(PRINCIPAL, newPwd); + assertFalse(_database.verifyPassword(PRINCIPAL_USERNAME, PASSWORD.toCharArray())); + assertTrue(_database.verifyPassword(PRINCIPAL_USERNAME, newPwd)); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java new file mode 100644 index 0000000000..abb0b15a76 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/HashedUserTest.java @@ -0,0 +1,83 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.database; + +import junit.framework.TestCase; + +import java.io.UnsupportedEncodingException; + +/* + Note User is mainly tested by Base64MD5PFPDTest this is just to catch the extra methods + */ +public class HashedUserTest extends TestCase +{ + + private String USERNAME = "username"; + private String PASSWORD = "password"; + private String B64_ENCODED_PASSWORD = "cGFzc3dvcmQ="; + + public void testToLongArrayConstructor() + { + try + { + HashedUser user = new HashedUser(new String[]{USERNAME, PASSWORD, USERNAME}); + fail("Error expected"); + } + catch (IllegalArgumentException e) + { + assertEquals("User Data should be length 2, username, password", e.getMessage()); + } + + } + + public void testArrayConstructor() + { + HashedUser user = new HashedUser(new String[]{USERNAME, B64_ENCODED_PASSWORD}); + assertEquals("Username incorrect", USERNAME, user.getName()); + int index = 0; + + char[] hash = B64_ENCODED_PASSWORD.toCharArray(); + + try + { + for (byte c : user.getEncodedPassword()) + { + assertEquals("Password incorrect", hash[index], (char) c); + index++; + } + } + catch (Exception e) + { + fail(e.getMessage()); + } + + hash = PASSWORD.toCharArray(); + + index=0; + for (char c : user.getPassword()) + { + assertEquals("Password incorrect", hash[index], c); + index++; + } + + } +} + diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java new file mode 100644 index 0000000000..eecbcdf38d --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabaseTest.java @@ -0,0 +1,415 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.database; + +import junit.framework.TestCase; + +import org.apache.qpid.server.security.auth.UsernamePrincipal; + +import javax.security.auth.login.AccountNotFoundException; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class PlainPasswordFilePrincipalDatabaseTest extends TestCase +{ + + private static final String TEST_COMMENT = "# Test Comment"; + private static final String TEST_PASSWORD = "testPassword"; + private static final char[] TEST_PASSWORD_CHARS = TEST_PASSWORD.toCharArray(); + private static final String TEST_USERNAME = "testUser"; + + private Principal _principal = new UsernamePrincipal(TEST_USERNAME); + private PlainPasswordFilePrincipalDatabase _database; + private List _testPwdFiles = new ArrayList(); + + public void setUp() throws Exception + { + _database = new PlainPasswordFilePrincipalDatabase(); + _testPwdFiles.clear(); + } + + public void tearDown() throws Exception + { + //clean up any additional files and their backups + for(File f : _testPwdFiles) + { + File oldPwdFile = new File(f.getAbsolutePath() + ".old"); + if(oldPwdFile.exists()) + { + oldPwdFile.delete(); + } + + f.delete(); + } + } + + // ******* Test Methods ********** // + + public void testCreatePrincipal() + { + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + final String CREATED_PASSWORD = "guest"; + final String CREATED_USERNAME = "createdUser"; + + Principal principal = new Principal() + { + public String getName() + { + return CREATED_USERNAME; + } + }; + + assertTrue("New user not created.", _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray())); + + loadPasswordFile(testFile); + + assertNotNull("Created User was not saved", _database.getUser(CREATED_USERNAME)); + + assertFalse("Duplicate user created.", _database.createPrincipal(principal, CREATED_PASSWORD.toCharArray())); + + testFile.delete(); + } + + public void testCreatePrincipalIsSavedToFile() + { + + File testFile = createPasswordFile(1, 0); + + loadPasswordFile(testFile); + + Principal principal = new Principal() + { + public String getName() + { + return TEST_USERNAME; + } + }; + + _database.createPrincipal(principal, TEST_PASSWORD_CHARS); + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", TEST_USERNAME, result[0]); + assertEquals("Password not correct,", TEST_PASSWORD, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + testFile.delete(); + } + + public void testDeletePrincipal() + { + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal user = _database.getUser(TEST_USERNAME + "0"); + assertNotNull("Generated user not present.", user); + + try + { + _database.deletePrincipal(user); + } + catch (AccountNotFoundException e) + { + fail("User should be present" + e.getMessage()); + } + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + loadPasswordFile(testFile); + + try + { + _database.deletePrincipal(user); + fail("User should not be present"); + } + catch (AccountNotFoundException e) + { + //pass + } + + assertNull("Deleted user still present.", _database.getUser(TEST_USERNAME + "0")); + + testFile.delete(); + } + + public void testGetUsers() + { + int USER_COUNT = 10; + File testFile = createPasswordFile(1, USER_COUNT); + + loadPasswordFile(testFile); + + Principal user = _database.getUser("MISSING_USERNAME"); + assertNull("Missing user present.", user); + + List users = _database.getUsers(); + + assertNotNull("Users list is null.", users); + + assertEquals(USER_COUNT, users.size()); + + boolean[] verify = new boolean[USER_COUNT]; + for (int i = 0; i < USER_COUNT; i++) + { + Principal principal = users.get(i); + + assertNotNull("Generated user not present.", principal); + + String name = principal.getName(); + + int id = Integer.parseInt(name.substring(TEST_USERNAME.length())); + + assertFalse("Duplicated username retrieve", verify[id]); + verify[id] = true; + } + + for (int i = 0; i < USER_COUNT; i++) + { + assertTrue("User " + i + " missing", verify[i]); + } + + testFile.delete(); + } + + public void testUpdatePasswordIsSavedToFile() + { + + File testFile = createPasswordFile(1, 1); + + loadPasswordFile(testFile); + + Principal testUser = _database.getUser(TEST_USERNAME + "0"); + + assertNotNull(testUser); + + String NEW_PASSWORD = "NewPassword"; + try + { + _database.updatePassword(testUser, NEW_PASSWORD.toCharArray()); + } + catch (AccountNotFoundException e) + { + fail(e.toString()); + } + + try + { + BufferedReader reader = new BufferedReader(new FileReader(testFile)); + + assertTrue("File has no content", reader.ready()); + + assertEquals("Comment line has been corrupted.", TEST_COMMENT, reader.readLine()); + + assertTrue("File is missing user data.", reader.ready()); + + String userLine = reader.readLine(); + + String[] result = Pattern.compile(":").split(userLine); + + assertEquals("User line not complete '" + userLine + "'", 2, result.length); + + assertEquals("Username not correct,", TEST_USERNAME + "0", result[0]); + assertEquals("New Password not correct,", NEW_PASSWORD, result[1]); + + assertFalse("File has more content", reader.ready()); + + } + catch (IOException e) + { + fail("Unable to valdate file contents due to:" + e.getMessage()); + } + testFile.delete(); + } + + public void testSetPasswordFileWithMissingFile() + { + try + { + _database.open(new File("DoesntExist")); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage(), fnfe.getMessage().startsWith("Cannot find password file")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + } + + public void testSetPasswordFileWithReadOnlyFile() + { + + File testFile = createPasswordFile(0, 0); + + testFile.setReadOnly(); + + try + { + _database.open(testFile); + } + catch (FileNotFoundException fnfe) + { + assertTrue(fnfe.getMessage().startsWith("Cannot read password file ")); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + + testFile.delete(); + } + + private void createUserPrincipal() throws IOException + { + File testFile = createPasswordFile(0, 0); + loadPasswordFile(testFile); + + _database.createPrincipal(_principal, TEST_PASSWORD_CHARS); + Principal newPrincipal = _database.getUser(TEST_USERNAME); + assertNotNull(newPrincipal); + assertEquals(_principal.getName(), newPrincipal.getName()); + } + + public void testVerifyPassword() throws IOException, AccountNotFoundException + { + createUserPrincipal(); + assertFalse(_database.verifyPassword(TEST_USERNAME, new char[]{})); + assertFalse(_database.verifyPassword(TEST_USERNAME, "massword".toCharArray())); + assertTrue(_database.verifyPassword(TEST_USERNAME, TEST_PASSWORD_CHARS)); + + try + { + _database.verifyPassword("made.up.username", TEST_PASSWORD_CHARS); + fail("Should not have been able to verify this non-existant users password."); + } + catch (AccountNotFoundException e) + { + // pass + } + } + + public void testUpdatePassword() throws IOException, AccountNotFoundException + { + createUserPrincipal(); + char[] newPwd = "newpassword".toCharArray(); + _database.updatePassword(_principal, newPwd); + assertFalse(_database.verifyPassword(TEST_USERNAME, TEST_PASSWORD_CHARS)); + assertTrue(_database.verifyPassword(TEST_USERNAME, newPwd)); + } + + + + // *********** Utility Methods ******** // + + private File createPasswordFile(int commentLines, int users) + { + try + { + File testFile = File.createTempFile(this.getClass().getName(),"tmp"); + testFile.deleteOnExit(); + + BufferedWriter writer = new BufferedWriter(new FileWriter(testFile)); + + for (int i = 0; i < commentLines; i++) + { + writer.write(TEST_COMMENT); + writer.newLine(); + } + + for (int i = 0; i < users; i++) + { + writer.write(TEST_USERNAME + i + ":" + TEST_PASSWORD); + writer.newLine(); + } + + writer.flush(); + writer.close(); + + _testPwdFiles.add(testFile); + + return testFile; + + } + catch (IOException e) + { + fail("Unable to create test password file." + e.getMessage()); + } + + return null; + } + + private void loadPasswordFile(File file) + { + try + { + _database.open(file); + } + catch (IOException e) + { + fail("Password File was not created." + e.getMessage()); + } + } + + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java new file mode 100644 index 0000000000..44faa57dbe --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/database/PlainUserTest.java @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.database; + +import junit.framework.TestCase; + +/* + Note PlainUser is mainly tested by PlainPFPDTest, this is just to catch the extra methods + */ +public class PlainUserTest extends TestCase +{ + + private String USERNAME = "username"; + private String PASSWORD = "password"; + + public void testTooLongArrayConstructor() + { + try + { + PlainUser user = new PlainUser(new String[]{USERNAME, PASSWORD, USERNAME}); + fail("Error expected"); + } + catch (IllegalArgumentException e) + { + assertEquals("User Data should be length 2, username, password", e.getMessage()); + } + } + + public void testStringArrayConstructor() + { + PlainUser user = new PlainUser(new String[]{USERNAME, PASSWORD}); + assertEquals("Username incorrect", USERNAME, user.getName()); + int index = 0; + + char[] password = PASSWORD.toCharArray(); + + try + { + for (byte c : user.getEncodedPassword()) + { + assertEquals("Password incorrect", password[index], (char) c); + index++; + } + } + catch (Exception e) + { + fail(e.getMessage()); + } + + password = PASSWORD.toCharArray(); + + index=0; + for (char c : user.getPassword()) + { + assertEquals("Password incorrect", password[index], c); + index++; + } + } +} + diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/jmx/JMXPasswordAuthenticatorTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/jmx/JMXPasswordAuthenticatorTest.java new file mode 100644 index 0000000000..a4dd97e6a1 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/jmx/JMXPasswordAuthenticatorTest.java @@ -0,0 +1,259 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.jmx; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.Principal; +import java.util.regex.Pattern; + +import javax.security.auth.Subject; + +import junit.framework.TestCase; + +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.security.auth.jmx.JMXPasswordAuthenticator; +import org.apache.qpid.server.security.auth.SubjectAuthenticationResult; +import org.apache.qpid.server.security.SecurityManager; + +/** + * Tests the JMXPasswordAuthenticator and its collaboration with the AuthenticationManager. + * + */ +public class JMXPasswordAuthenticatorTest extends TestCase +{ + private static final String USERNAME = "guest"; + private static final String PASSWORD = "password"; + + private final Broker _broker = mock(Broker.class); + private final SecurityManager _securityManager = mock(SecurityManager.class); + private final Subject _loginSubject = new Subject(); + private final String[] _credentials = new String[] {USERNAME, PASSWORD}; + + private JMXPasswordAuthenticator _rmipa; + + private SubjectCreator _usernamePasswordOkaySuvjectCreator = createMockSubjectCreator(true, null); + private SubjectCreator _badPasswordSubjectCreator = createMockSubjectCreator(false, null); + + protected void setUp() throws Exception + { + when(_broker.getSecurityManager()).thenReturn(_securityManager); + _rmipa = new JMXPasswordAuthenticator(_broker, new InetSocketAddress(8999)); + } + + /** + * Tests a successful authentication. Ensures that the expected subject is returned. + */ + public void testAuthenticationSuccess() + { + when(_broker.getSubjectCreator(any(SocketAddress.class))).thenReturn(_usernamePasswordOkaySuvjectCreator); + when(_securityManager.accessManagement()).thenReturn(true); + + Subject newSubject = _rmipa.authenticate(_credentials); + assertSame("Subject must be unchanged", _loginSubject, newSubject); + } + + /** + * Tests a unsuccessful authentication. + */ + public void testUsernameOrPasswordInvalid() + { + when(_broker.getSubjectCreator(any(SocketAddress.class))).thenReturn(_badPasswordSubjectCreator); + + try + { + _rmipa.authenticate(_credentials); + fail("Exception not thrown"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + JMXPasswordAuthenticator.INVALID_CREDENTIALS, se.getMessage()); + } + } + + public void testAuthorisationFailure() + { + when(_broker.getSubjectCreator(any(SocketAddress.class))).thenReturn(_usernamePasswordOkaySuvjectCreator); + when(_securityManager.accessManagement()).thenReturn(false); + + try + { + _rmipa.authenticate(_credentials); + fail("Exception not thrown"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + JMXPasswordAuthenticator.USER_NOT_AUTHORISED_FOR_MANAGEMENT, se.getMessage()); + } + } + + public void testSubjectCreatorInternalFailure() + { + final Exception mockAuthException = new Exception("Mock Auth system failure"); + SubjectCreator subjectCreator = createMockSubjectCreator(false, mockAuthException); + when(_broker.getSubjectCreator(any(SocketAddress.class))).thenReturn(subjectCreator); + + try + { + _rmipa.authenticate(_credentials); + fail("Exception not thrown"); + } + catch (SecurityException se) + { + assertEquals("Initial cause not found", mockAuthException, se.getCause()); + } + } + + /** + * Tests case where authentication manager is not set. + */ + public void testNullSubjectCreator() throws Exception + { + when(_broker.getSubjectCreator(any(SocketAddress.class))).thenReturn(null); + + try + { + _rmipa.authenticate(_credentials); + fail("SecurityException expected due to lack of authentication manager"); + } + catch (SecurityException se) + { + assertTrue("Unexpected exception message", Pattern.matches("Can't get subject creator for .*:8999", se.getMessage())); + } + } + + /** + * Tests case where arguments are non-Strings.. + */ + public void testWithNonStringArrayArgument() + { + // Test handling of non-string credential's + final Object[] objCredentials = new Object[]{USERNAME, PASSWORD}; + try + { + _rmipa.authenticate(objCredentials); + fail("SecurityException expected due to non string[] credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + JMXPasswordAuthenticator.SHOULD_BE_STRING_ARRAY, se.getMessage()); + } + } + + /** + * Tests case where there are too many, too few or null arguments. + */ + public void testWithIllegalNumberOfArguments() + { + String[] credentials; + + // Test handling of incorrect number of credentials + try + { + credentials = new String[]{USERNAME, PASSWORD, PASSWORD}; + _rmipa.authenticate(credentials); + fail("SecurityException expected due to supplying wrong number of credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + JMXPasswordAuthenticator.SHOULD_HAVE_2_ELEMENTS, se.getMessage()); + } + + // Test handling of null credentials + try + { + //send a null array + credentials = null; + _rmipa.authenticate(credentials); + fail("SecurityException expected due to not supplying an array of credentials"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + JMXPasswordAuthenticator.CREDENTIALS_REQUIRED, se.getMessage()); + } + + try + { + //send a null password + credentials = new String[]{USERNAME, null}; + _rmipa.authenticate(credentials); + fail("SecurityException expected due to sending a null password"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + JMXPasswordAuthenticator.SHOULD_BE_NON_NULL, se.getMessage()); + } + + try + { + //send a null username + credentials = new String[]{null, PASSWORD}; + _rmipa.authenticate(credentials); + fail("SecurityException expected due to sending a null username"); + } + catch (SecurityException se) + { + assertEquals("Unexpected exception message", + JMXPasswordAuthenticator.SHOULD_BE_NON_NULL, se.getMessage()); + } + } + + private SubjectCreator createMockSubjectCreator(final boolean successfulAuth, final Exception exception) + { + SubjectCreator subjectCreator = mock(SubjectCreator.class); + + SubjectAuthenticationResult subjectAuthenticationResult; + + if (exception != null) { + + subjectAuthenticationResult = new SubjectAuthenticationResult( + new AuthenticationResult(AuthenticationStatus.ERROR, exception)); + } + else if (successfulAuth) + { + + subjectAuthenticationResult = new SubjectAuthenticationResult( + new AuthenticationResult(mock(Principal.class)), _loginSubject); + } + else + { + subjectAuthenticationResult = new SubjectAuthenticationResult(new AuthenticationResult(AuthenticationStatus.CONTINUE)); + } + + when(subjectCreator.authenticate(anyString(), anyString())).thenReturn(subjectAuthenticationResult); + + return subjectCreator; + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerTest.java new file mode 100644 index 0000000000..cfeb7c525b --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/AnonymousAuthenticationManagerTest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ +package org.apache.qpid.server.security.auth.manager; + +import static org.apache.qpid.server.security.auth.AuthenticatedPrincipalTestHelper.assertOnlyContainsWrapped; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.test.utils.QpidTestCase; + +public class AnonymousAuthenticationManagerTest extends QpidTestCase +{ + private AuthenticationManager _manager = new AnonymousAuthenticationManager(); + + public void tearDown() throws Exception + { + if(_manager != null) + { + _manager = null; + } + } + + public void testGetMechanisms() throws Exception + { + assertEquals("ANONYMOUS", _manager.getMechanisms()); + } + + public void testCreateSaslServer() throws Exception + { + SaslServer server = _manager.createSaslServer("ANONYMOUS", "example.example.com", null); + + assertEquals("Sasl Server mechanism name is not as expected", "ANONYMOUS", server.getMechanismName()); + + try + { + server = _manager.createSaslServer("PLAIN", "example.example.com", null); + fail("Expected creating SaslServer with incorrect mechanism to throw an exception"); + } + catch (SaslException e) + { + // pass + } + } + + public void testAuthenticate() throws Exception + { + SaslServer saslServer = _manager.createSaslServer("ANONYMOUS", "example.example.com", null); + AuthenticationResult result = _manager.authenticate(saslServer, new byte[0]); + assertNotNull(result); + assertEquals("Expected authentication to be successful", + AuthenticationResult.AuthenticationStatus.SUCCESS, + result.getStatus()); + + assertOnlyContainsWrapped(AnonymousAuthenticationManager.ANONYMOUS_PRINCIPAL, result.getPrincipals()); + } + + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/Base64MD5PasswordFileAuthenticationManagerFactoryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/Base64MD5PasswordFileAuthenticationManagerFactoryTest.java new file mode 100644 index 0000000000..04e09e073f --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/Base64MD5PasswordFileAuthenticationManagerFactoryTest.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.manager; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; + +public class Base64MD5PasswordFileAuthenticationManagerFactoryTest extends TestCase +{ + AuthenticationManagerFactory _factory = new Base64MD5PasswordFileAuthenticationManagerFactory(); + private Map _configuration = new HashMap(); + private File _emptyPasswordFile; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _emptyPasswordFile = File.createTempFile(getName(), "passwd"); + _emptyPasswordFile.deleteOnExit(); + } + + public void testBase64MD5InstanceCreated() throws Exception + { + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_TYPE, Base64MD5PasswordFileAuthenticationManagerFactory.PROVIDER_TYPE); + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_PATH, _emptyPasswordFile.getAbsolutePath()); + + AuthenticationManager manager = _factory.createInstance(_configuration); + assertNotNull(manager); + assertTrue(manager instanceof PrincipalDatabaseAuthenticationManager); + assertTrue(((PrincipalDatabaseAuthenticationManager)manager).getPrincipalDatabase() instanceof Base64MD5PasswordFilePrincipalDatabase); + } + + public void testPasswordFileNotFound() throws Exception + { + //delete the file + _emptyPasswordFile.delete(); + + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_TYPE, Base64MD5PasswordFileAuthenticationManagerFactory.PROVIDER_TYPE); + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_PATH, _emptyPasswordFile.getAbsolutePath()); + + try + { + _factory.createInstance(_configuration); + } + catch (RuntimeException re) + { + assertTrue(re.getCause() instanceof FileNotFoundException); + } + } + + public void testReturnsNullWhenNoConfig() throws Exception + { + AuthenticationManager manager = _factory.createInstance(_configuration); + assertNull(manager); + } + + public void testReturnsNullWhenConfigForOtherAuthManagerType() throws Exception + { + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_TYPE, "other-auth-manager"); + AuthenticationManager manager = _factory.createInstance(_configuration); + assertNull(manager); + } + + public void testReturnsNullWhenConfigForPlainPDImplementationNoPasswordFileValueSpecified() throws Exception + { + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_TYPE, Base64MD5PasswordFileAuthenticationManagerFactory.PROVIDER_TYPE); + + AuthenticationManager manager = _factory.createInstance(_configuration); + assertNull(manager); + } + + @Override + protected void tearDown() throws Exception + { + try + { + if (_emptyPasswordFile == null && _emptyPasswordFile.exists()) + { + _emptyPasswordFile.delete(); + } + } + finally + { + super.tearDown(); + } + } +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerTest.java new file mode 100644 index 0000000000..61506777c5 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/ExternalAuthenticationManagerTest.java @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.qpid.server.security.auth.manager; + +import static org.apache.qpid.server.security.auth.AuthenticatedPrincipalTestHelper.assertOnlyContainsWrapped; + +import javax.security.auth.x500.X500Principal; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.test.utils.QpidTestCase; + +public class ExternalAuthenticationManagerTest extends QpidTestCase +{ + private AuthenticationManager _manager = new ExternalAuthenticationManager(false); + private AuthenticationManager _managerUsingFullDN = new ExternalAuthenticationManager(true); + + public void testGetMechanisms() throws Exception + { + assertEquals("EXTERNAL", _manager.getMechanisms()); + } + + public void testCreateSaslServer() throws Exception + { + createSaslServerTestImpl(_manager); + } + + public void testCreateSaslServerUsingFullDN() throws Exception + { + createSaslServerTestImpl(_managerUsingFullDN); + } + + public void createSaslServerTestImpl(AuthenticationManager manager) throws Exception + { + SaslServer server = manager.createSaslServer("EXTERNAL", "example.example.com", null); + + assertEquals("Sasl Server mechanism name is not as expected", "EXTERNAL", server.getMechanismName()); + + try + { + server = manager.createSaslServer("PLAIN", "example.example.com", null); + fail("Expected creating SaslServer with incorrect mechanism to throw an exception"); + } + catch (SaslException e) + { + // pass + } + } + + /** + * Test behaviour of the authentication when the useFullDN attribute is set true + * and the username is taken directly as the externally supplied Principal + */ + public void testAuthenticateWithFullDN() throws Exception + { + X500Principal principal = new X500Principal("CN=person, DC=example, DC=com"); + SaslServer saslServer = _managerUsingFullDN.createSaslServer("EXTERNAL", "example.example.com", principal); + + AuthenticationResult result = _managerUsingFullDN.authenticate(saslServer, new byte[0]); + assertNotNull(result); + assertEquals("Expected authentication to be successful", + AuthenticationResult.AuthenticationStatus.SUCCESS, + result.getStatus()); + + assertOnlyContainsWrapped(principal, result.getPrincipals()); + + saslServer = _managerUsingFullDN.createSaslServer("EXTERNAL", "example.example.com", null); + result = _managerUsingFullDN.authenticate(saslServer, new byte[0]); + + assertNotNull(result); + assertEquals("Expected authentication to be unsuccessful", + AuthenticationResult.AuthenticationStatus.ERROR, + result.getStatus()); + } + + /** + * Test behaviour of the authentication when parsing the username from + * the Principals DN as @...... + */ + public void testAuthenticateWithUsernameBasedOnCNAndDC() throws Exception + { + X500Principal principal; + SaslServer saslServer; + AuthenticationResult result; + UsernamePrincipal expectedPrincipal; + + // DN contains only CN + principal = new X500Principal("CN=person"); + expectedPrincipal = new UsernamePrincipal("person"); + saslServer = _manager.createSaslServer("EXTERNAL", "example.example.com", principal); + + result = _manager.authenticate(saslServer, new byte[0]); + assertNotNull(result); + assertEquals("Expected authentication to be successful", + AuthenticationResult.AuthenticationStatus.SUCCESS, + result.getStatus()); + assertOnlyContainsWrapped(expectedPrincipal, result.getPrincipals()); + + // Null principal + saslServer = _manager.createSaslServer("EXTERNAL", "example.example.com", null); + result = _manager.authenticate(saslServer, new byte[0]); + + assertNotNull(result); + assertEquals("Expected authentication to be unsuccessful", + AuthenticationResult.AuthenticationStatus.ERROR, + result.getStatus()); + + // DN doesn't contain CN + principal = new X500Principal("DC=example, DC=com, O=My Company Ltd, L=Newbury, ST=Berkshire, C=GB"); + saslServer = _manager.createSaslServer("EXTERNAL", "example.example.com", principal); + result = _manager.authenticate(saslServer, new byte[0]); + + assertNotNull(result); + assertEquals("Expected authentication to be unsuccessful", + AuthenticationResult.AuthenticationStatus.ERROR, + result.getStatus()); + + // DN contains empty CN + principal = new X500Principal("CN=, DC=example, DC=com, O=My Company Ltd, L=Newbury, ST=Berkshire, C=GB"); + saslServer = _manager.createSaslServer("EXTERNAL", "example.example.com", principal); + result = _manager.authenticate(saslServer, new byte[0]); + + assertNotNull(result); + assertEquals("Expected authentication to be unsuccessful", + AuthenticationResult.AuthenticationStatus.ERROR, + result.getStatus()); + + // DN contains CN and DC + principal = new X500Principal("CN=person, DC=example, DC=com"); + expectedPrincipal = new UsernamePrincipal("person@example.com"); + saslServer = _manager.createSaslServer("EXTERNAL", "example.example.com", principal); + + result = _manager.authenticate(saslServer, new byte[0]); + assertNotNull(result); + assertEquals("Expected authentication to be successful", + AuthenticationResult.AuthenticationStatus.SUCCESS, + result.getStatus()); + assertOnlyContainsWrapped(expectedPrincipal, result.getPrincipals()); + + // DN contains CN and DC and other components + principal = new X500Principal("CN=person, DC=example, DC=com, O=My Company Ltd, L=Newbury, ST=Berkshire, C=GB"); + expectedPrincipal = new UsernamePrincipal("person@example.com"); + saslServer = _manager.createSaslServer("EXTERNAL", "example.example.com", principal); + + result = _manager.authenticate(saslServer, new byte[0]); + assertNotNull(result); + assertEquals("Expected authentication to be successful", + AuthenticationResult.AuthenticationStatus.SUCCESS, + result.getStatus()); + assertOnlyContainsWrapped(expectedPrincipal, result.getPrincipals()); + + // DN contains CN and DC and other components + principal = new X500Principal("CN=person, O=My Company Ltd, L=Newbury, ST=Berkshire, C=GB"); + expectedPrincipal = new UsernamePrincipal("person"); + saslServer = _manager.createSaslServer("EXTERNAL", "example.example.com", principal); + + result = _manager.authenticate(saslServer, new byte[0]); + assertNotNull(result); + assertEquals("Expected authentication to be successful", + AuthenticationResult.AuthenticationStatus.SUCCESS, + result.getStatus()); + assertOnlyContainsWrapped(expectedPrincipal, result.getPrincipals()); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/PlainPasswordFileAuthenticationManagerFactoryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/PlainPasswordFileAuthenticationManagerFactoryTest.java new file mode 100644 index 0000000000..cc11a94db8 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/PlainPasswordFileAuthenticationManagerFactoryTest.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 java.io.File; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.qpid.server.plugin.AuthenticationManagerFactory; +import org.apache.qpid.server.security.auth.database.PlainPasswordFilePrincipalDatabase; + +public class PlainPasswordFileAuthenticationManagerFactoryTest extends TestCase +{ + AuthenticationManagerFactory _factory = new PlainPasswordFileAuthenticationManagerFactory(); + private Map _configuration = new HashMap(); + private File _emptyPasswordFile; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _emptyPasswordFile = File.createTempFile(getName(), "passwd"); + _emptyPasswordFile.deleteOnExit(); + } + + public void testPlainInstanceCreated() throws Exception + { + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_TYPE, PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE); + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_PATH, _emptyPasswordFile.getAbsolutePath()); + + AuthenticationManager manager = _factory.createInstance(_configuration); + assertNotNull(manager); + assertTrue(manager instanceof PrincipalDatabaseAuthenticationManager); + assertTrue(((PrincipalDatabaseAuthenticationManager)manager).getPrincipalDatabase() instanceof PlainPasswordFilePrincipalDatabase); + } + + public void testPasswordFileNotFound() throws Exception + { + //delete the file + _emptyPasswordFile.delete(); + + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_TYPE, PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE); + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_PATH, _emptyPasswordFile.getAbsolutePath()); + + AuthenticationManager manager = _factory.createInstance(_configuration); + + assertNotNull(manager); + assertTrue(manager instanceof PrincipalDatabaseAuthenticationManager); + assertTrue(((PrincipalDatabaseAuthenticationManager)manager).getPrincipalDatabase() instanceof PlainPasswordFilePrincipalDatabase); + } + + public void testReturnsNullWhenNoConfig() throws Exception + { + AuthenticationManager manager = _factory.createInstance(_configuration); + assertNull(manager); + } + + public void testReturnsNullWhenConfigForOtherAuthManagerType() throws Exception + { + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_TYPE, "other-auth-manager"); + AuthenticationManager manager = _factory.createInstance(_configuration); + assertNull(manager); + } + + public void testReturnsNullWhenConfigForPlainPDImplementationNoPasswordFileValueSpecified() throws Exception + { + _configuration.put(AbstractPrincipalDatabaseAuthManagerFactory.ATTRIBUTE_TYPE, PlainPasswordFileAuthenticationManagerFactory.PROVIDER_TYPE); + + AuthenticationManager manager = _factory.createInstance(_configuration); + assertNull(manager); + } + + @Override + protected void tearDown() throws Exception + { + try + { + if (_emptyPasswordFile == null && _emptyPasswordFile.exists()) + { + _emptyPasswordFile.delete(); + } + } + finally + { + super.tearDown(); + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java new file mode 100644 index 0000000000..cba6058426 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManagerTest.java @@ -0,0 +1,348 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.manager; + +import static org.apache.qpid.server.security.auth.AuthenticatedPrincipalTestHelper.assertOnlyContainsWrapped; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.security.Principal; +import java.util.List; +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; + +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.security.auth.UsernamePrincipal; +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.UsernamePasswordInitialiser; +import org.apache.qpid.test.utils.QpidTestCase; + +/** + * Tests the public methods of PrincipalDatabaseAuthenticationManager. + * + */ +public class PrincipalDatabaseAuthenticationManagerTest extends QpidTestCase +{ + private static final String LOCALHOST = "localhost"; + private static final String MOCK_MECH_NAME = "MOCK-MECH-NAME"; + private static final UsernamePrincipal PRINCIPAL = new UsernamePrincipal("guest"); + + private AuthenticationManager _manager = null; // Class under test + private PrincipalDatabase _principalDatabase; + private String _passwordFileLocation; + + @Override + public void setUp() throws Exception + { + super.setUp(); + _passwordFileLocation = TMP_FOLDER + File.separator + PrincipalDatabaseAuthenticationManagerTest.class.getSimpleName() + "-" + getName(); + deletePasswordFileIfExists(); + } + + @Override + public void tearDown() throws Exception + { + try + { + if (_manager != null) + { + _manager.close(); + } + } + finally + { + deletePasswordFileIfExists(); + } + super.tearDown(); + } + + private void setupMocks() throws Exception + { + _principalDatabase = mock(PrincipalDatabase.class); + + when(_principalDatabase.getMechanisms()).thenReturn(MOCK_MECH_NAME); + when(_principalDatabase.createSaslServer(MOCK_MECH_NAME, LOCALHOST, null)).thenReturn(new MySaslServer(false, true)); + + _manager = new PrincipalDatabaseAuthenticationManager(_principalDatabase, _passwordFileLocation); + _manager.initialise(); + } + + public void testInitialiseWhenPasswordFileNotFound() throws Exception + { + _principalDatabase = new PlainPasswordFilePrincipalDatabase(); + _manager = new PrincipalDatabaseAuthenticationManager(_principalDatabase, _passwordFileLocation); + + try + { + _manager.initialise(); + fail("Initialisiation should fail when users file does not exist"); + } + catch (IllegalConfigurationException e) + { + assertTrue(e.getCause() instanceof FileNotFoundException); + } + } + + public void testInitialiseWhenPasswordFileExists() throws Exception + { + _principalDatabase = new PlainPasswordFilePrincipalDatabase(); + _manager = new PrincipalDatabaseAuthenticationManager(_principalDatabase, _passwordFileLocation); + + File f = new File(_passwordFileLocation); + f.createNewFile(); + FileOutputStream fos = null; + try + { + fos = new FileOutputStream(f); + fos.write("admin:admin".getBytes()); + } + finally + { + if (fos != null) + { + fos.close(); + } + } + _manager.initialise(); + List users = _principalDatabase.getUsers(); + assertEquals("Unexpected uses size", 1, users.size()); + Principal p = _principalDatabase.getUser("admin"); + assertEquals("Unexpected principal name", "admin", p.getName()); + } + + /** + * Tests that the SASL factory method createSaslServer correctly + * returns a non-null implementation. + */ + public void testSaslMechanismCreation() throws Exception + { + setupMocks(); + + SaslServer server = _manager.createSaslServer(MOCK_MECH_NAME, LOCALHOST, null); + assertNotNull(server); + // Merely tests the creation of the mechanism. Mechanisms themselves are tested + // by their own tests. + } + + /** + * Tests that the authenticate method correctly interprets an + * authentication success. + * + */ + public void testSaslAuthenticationSuccess() throws Exception + { + setupMocks(); + + SaslServer testServer = createTestSaslServer(true, false); + + AuthenticationResult result = _manager.authenticate(testServer, "12345".getBytes()); + + assertOnlyContainsWrapped(PRINCIPAL, result.getPrincipals()); + assertEquals(AuthenticationStatus.SUCCESS, result.getStatus()); + } + + /** + * + * Tests that the authenticate method correctly interprets an + * authentication not complete. + * + */ + public void testSaslAuthenticationNotCompleted() throws Exception + { + setupMocks(); + + SaslServer testServer = createTestSaslServer(false, false); + + AuthenticationResult result = _manager.authenticate(testServer, "12345".getBytes()); + assertEquals("Principals was not expected size", 0, result.getPrincipals().size()); + + assertEquals(AuthenticationStatus.CONTINUE, result.getStatus()); + } + + /** + * + * Tests that the authenticate method correctly interprets an + * authentication error. + * + */ + public void testSaslAuthenticationError() throws Exception + { + setupMocks(); + + SaslServer testServer = createTestSaslServer(false, true); + + AuthenticationResult result = _manager.authenticate(testServer, "12345".getBytes()); + assertEquals("Principals was not expected size", 0, result.getPrincipals().size()); + assertEquals(AuthenticationStatus.ERROR, result.getStatus()); + } + + public void testNonSaslAuthenticationSuccess() throws Exception + { + setupMocks(); + + when(_principalDatabase.verifyPassword("guest", "guest".toCharArray())).thenReturn(true); + + AuthenticationResult result = _manager.authenticate("guest", "guest"); + assertOnlyContainsWrapped(PRINCIPAL, result.getPrincipals()); + assertEquals(AuthenticationStatus.SUCCESS, result.getStatus()); + } + + public void testNonSaslAuthenticationNotCompleted() throws Exception + { + setupMocks(); + + when(_principalDatabase.verifyPassword("guest", "wrongpassword".toCharArray())).thenReturn(false); + + AuthenticationResult result = _manager.authenticate("guest", "wrongpassword"); + assertEquals("Principals was not expected size", 0, result.getPrincipals().size()); + assertEquals(AuthenticationStatus.CONTINUE, result.getStatus()); + } + + public void testOnCreate() throws Exception + { + setupMocks(); + + _manager.onCreate(); + assertTrue("Password file was not created", new File(_passwordFileLocation).exists()); + } + + public void testOnDelete() throws Exception + { + setupMocks(); + + _manager.onCreate(); + assertTrue("Password file was not created", new File(_passwordFileLocation).exists()); + + _manager.onDelete(); + assertFalse("Password file was not deleted", new File(_passwordFileLocation).exists()); + } + + private void deletePasswordFileIfExists() + { + File passwordFile = new File(_passwordFileLocation); + if (passwordFile.exists()) + { + passwordFile.delete(); + } + } + + /** + * Test SASL implementation used to test the authenticate() method. + */ + private SaslServer createTestSaslServer(final boolean complete, final boolean throwSaslException) + { + return new MySaslServer(throwSaslException, complete); + } + + public static final class MySaslServer implements SaslServer + { + private final boolean _throwSaslException; + private final boolean _complete; + + public MySaslServer() + { + this(false, true); + } + + private MySaslServer(boolean throwSaslException, boolean complete) + { + _throwSaslException = throwSaslException; + _complete = complete; + } + + public String getMechanismName() + { + return null; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + if (_throwSaslException) + { + throw new SaslException("Mocked exception"); + } + return null; + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return _complete ? "guest" : null; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + return null; + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + return null; + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + } + } + + public static class MySaslServerFactory implements SaslServerFactory + { + @Override + public SaslServer createSaslServer(String mechanism, String protocol, + String serverName, Map props, CallbackHandler cbh) + throws SaslException + { + if (MOCK_MECH_NAME.equals(mechanism)) + { + return new MySaslServer(); + } + else + { + return null; + } + } + + @Override + public String[] getMechanismNames(Map props) + { + return new String[]{MOCK_MECH_NAME}; + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManagerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManagerTest.java new file mode 100644 index 0000000000..110206a83d --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleAuthenticationManagerTest.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.manager; + +import java.security.Principal; +import java.util.Set; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.AuthenticationResult.AuthenticationStatus; +import org.apache.qpid.server.security.auth.sasl.SaslUtil; +import org.apache.qpid.server.security.auth.sasl.plain.PlainSaslServer; +import org.apache.qpid.test.utils.QpidTestCase; + +public class SimpleAuthenticationManagerTest extends QpidTestCase +{ + private static final String TEST_USER = "testUser"; + private static final String TEST_PASSWORD = "testPassword"; + private AuthenticationManager _authenticationManager; + + public void setUp() throws Exception + { + super.setUp(); + _authenticationManager = new SimpleAuthenticationManager(TEST_USER, TEST_PASSWORD); + } + + public void testGetMechanisms() + { + assertEquals("Unexpected mechanisms", "PLAIN CRAM-MD5", _authenticationManager.getMechanisms()); + } + + public void testCreateSaslServerForUnsupportedMechanisms() throws Exception + { + String[] unsupported = new String[] { "EXTERNAL", "CRAM-MD5-HEX", "CRAM-MD5-HASHED", "ANONYMOUS", "GSSAPI"}; + for (int i = 0; i < unsupported.length; i++) + { + String mechanism = unsupported[i]; + try + { + _authenticationManager.createSaslServer(mechanism, "test", null); + fail("Mechanism " + mechanism + " should not be supported by SimpleAuthenticationManager"); + } + catch (SaslException e) + { + // pass + } + } + } + + public void testAuthenticateWithPlainSaslServer() throws Exception + { + AuthenticationResult result = authenticatePlain(TEST_USER, TEST_PASSWORD); + assertAuthenticated(result); + } + + public void testAuthenticateWithPlainSaslServerInvalidPassword() throws Exception + { + AuthenticationResult result = authenticatePlain(TEST_USER, "wrong-password"); + assertUnauthenticated(result); + } + + public void testAuthenticateWithPlainSaslServerInvalidUsername() throws Exception + { + AuthenticationResult result = authenticatePlain("wrong-user", TEST_PASSWORD); + assertUnauthenticated(result); + } + + public void testAuthenticateWithCramMd5SaslServer() throws Exception + { + AuthenticationResult result = authenticateCramMd5(TEST_USER, TEST_PASSWORD); + assertAuthenticated(result); + } + + public void testAuthenticateWithCramMd5SaslServerInvalidPassword() throws Exception + { + AuthenticationResult result = authenticateCramMd5(TEST_USER, "wrong-password"); + assertUnauthenticated(result); + } + + public void testAuthenticateWithCramMd5SaslServerInvalidUsername() throws Exception + { + AuthenticationResult result = authenticateCramMd5("wrong-user", TEST_PASSWORD); + assertUnauthenticated(result); + } + + public void testAuthenticateValidCredentials() + { + AuthenticationResult result = _authenticationManager.authenticate(TEST_USER, TEST_PASSWORD); + assertEquals("Unexpected authentication result", AuthenticationStatus.SUCCESS, result.getStatus()); + assertAuthenticated(result); + } + + public void testAuthenticateInvalidPassword() + { + AuthenticationResult result = _authenticationManager.authenticate(TEST_USER, "invalid"); + assertUnauthenticated(result); + } + + public void testAuthenticateInvalidUserName() + { + AuthenticationResult result = _authenticationManager.authenticate("invalid", TEST_PASSWORD); + assertUnauthenticated(result); + } + + private void assertAuthenticated(AuthenticationResult result) + { + assertEquals("Unexpected authentication result", AuthenticationStatus.SUCCESS, result.getStatus()); + Principal principal = result.getMainPrincipal(); + assertEquals("Unexpected principal name", TEST_USER, principal.getName()); + Set principals = result.getPrincipals(); + assertEquals("Unexpected principals size", 1, principals.size()); + assertEquals("Unexpected principal name", TEST_USER, principals.iterator().next().getName()); + } + + private void assertUnauthenticated(AuthenticationResult result) + { + assertEquals("Unexpected authentication result", AuthenticationStatus.ERROR, result.getStatus()); + assertNull("Unexpected principal", result.getMainPrincipal()); + Set principals = result.getPrincipals(); + assertEquals("Unexpected principals size", 0, principals.size()); + } + + private AuthenticationResult authenticatePlain(String userName, String userPassword) throws SaslException, Exception + { + PlainSaslServer ss = (PlainSaslServer) _authenticationManager.createSaslServer("PLAIN", "test", null); + byte[] response = SaslUtil.generatePlainClientResponse(userName, userPassword); + + return _authenticationManager.authenticate(ss, response); + } + + private AuthenticationResult authenticateCramMd5(String userName, String userPassword) throws SaslException, Exception + { + SaslServer ss = _authenticationManager.createSaslServer("CRAM-MD5", "test", null); + byte[] challenge = ss.evaluateResponse(new byte[0]); + byte[] response = SaslUtil.generateCramMD5ClientResponse(userName, userPassword, challenge); + + AuthenticationResult result = _authenticationManager.authenticate(ss, response); + return result; + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerFactoryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerFactoryTest.java new file mode 100644 index 0000000000..1424bee611 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/manager/SimpleLDAPAuthenticationManagerFactoryTest.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.manager; + +import java.util.HashMap; +import java.util.Map; + + +import junit.framework.TestCase; + +public class SimpleLDAPAuthenticationManagerFactoryTest extends TestCase +{ + private SimpleLDAPAuthenticationManagerFactory _factory = new SimpleLDAPAuthenticationManagerFactory(); + private Map _configuration = new HashMap(); + + public void testInstanceCreated() throws Exception + { + _configuration.put(SimpleLDAPAuthenticationManagerFactory.ATTRIBUTE_TYPE, SimpleLDAPAuthenticationManagerFactory.PROVIDER_TYPE); + _configuration.put("providerUrl", "ldaps://example.com:636/"); + _configuration.put("searchContext", "dc=example"); + + AuthenticationManager manager = _factory.createInstance(_configuration); + assertNotNull(manager); + } + + public void testReturnsNullWhenNoConfig() throws Exception + { + AuthenticationManager manager = _factory.createInstance(_configuration); + assertNull(manager); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexInitialiserTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexInitialiserTest.java new file mode 100644 index 0000000000..3079222b1c --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexInitialiserTest.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.security.auth.sasl; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; + +import junit.framework.TestCase; + +import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexInitialiser; +import org.apache.qpid.test.utils.TestFileUtils; +import org.apache.qpid.tools.security.Passwd; + +/** + * These tests ensure that the Hex wrapping that the initialiser performs does actually operate when the handle method is called. + */ +public class CRAMMD5HexInitialiserTest extends TestCase +{ + private static final String TEST_PASSWORD = "testPassword"; + private static final String TEST_USER = "testUser"; + private File _file; + + public void testHashedHex() throws Exception + { + perform(TEST_USER, getHash(TEST_PASSWORD)); + } + + public void perform(String user, char[] password) throws Exception + { + CRAMMD5HexInitialiser initialiser = new CRAMMD5HexInitialiser(); + + PrincipalDatabase db = new Base64MD5PasswordFilePrincipalDatabase(); + db.open(_file); + initialiser.initialise(db); + + PasswordCallback passwordCallback = new PasswordCallback("password:", false); + NameCallback usernameCallback = new NameCallback("user:", user); + + Callback[] callbacks = new Callback[]{usernameCallback, passwordCallback}; + + assertNull("The password was not null before the handle call.", passwordCallback.getPassword()); + initialiser.getCallbackHandler().handle(callbacks); + + assertArrayEquals(toHex(password), passwordCallback.getPassword()); + } + + public void setUp() throws Exception + { + super.setUp(); + _file = TestFileUtils.createTempFile(this, "password-file", new Passwd().getOutput(TEST_USER , TEST_PASSWORD)); + } + + public void tearDown() throws Exception + { + if (_file != null) + { + _file.delete(); + } + super.tearDown(); + } + + private char[] getHash(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException + { + + byte[] data = text.getBytes("utf-8"); + + MessageDigest md = MessageDigest.getInstance("MD5"); + + for (byte b : data) + { + md.update(b); + } + + byte[] digest = md.digest(); + + char[] hash = new char[digest.length]; + + int index = 0; + for (byte b : digest) + { + hash[index++] = (char) b; + } + + return hash; + } + + private void assertArrayEquals(char[] expected, char[] actual) + { + assertEquals("Arrays are not the same length", expected.length, actual.length); + + for (int index = 0; index < expected.length; index++) + { + assertEquals("Characters are not equal", expected[index], actual[index]); + } + } + + private char[] toHex(char[] password) + { + StringBuilder sb = new StringBuilder(); + for (char c : password) + { + //toHexString does not prepend 0 so we have to + if (((byte) c > -1) && (byte) c < 10) + { + sb.append(0); + } + + sb.append(Integer.toHexString(c & 0xFF)); + } + + //Extract the hex string as char[] + char[] hex = new char[sb.length()]; + + sb.getChars(0, sb.length(), hex, 0); + + return hex; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java new file mode 100644 index 0000000000..b3e929dd6c --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/CRAMMD5HexServerTest.java @@ -0,0 +1,227 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.security.auth.sasl; + +import junit.framework.TestCase; +import org.apache.commons.codec.binary.Hex; + +import org.apache.qpid.server.security.auth.database.Base64MD5PasswordFilePrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexInitialiser; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexSaslServer; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexServerFactory; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import java.io.File; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.Principal; + +/** + * Test for the CRAM-MD5-HEX SASL mechanism. + * + * This test case focuses on testing {@link CRAMMD5HexSaslServer} but also exercises + * collaborators {@link CRAMMD5HexInitialiser} and {@link Base64MD5PasswordFilePrincipalDatabase} + */ +public class CRAMMD5HexServerTest extends TestCase +{ + + private SaslServer _saslServer; // Class under test + private CRAMMD5HexServerFactory _saslFactory; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + CRAMMD5HexInitialiser _initializer = new CRAMMD5HexInitialiser(); + + //Use properties to create a PrincipalDatabase + Base64MD5PasswordFilePrincipalDatabase db = createTestPrincipalDatabase(); + assertEquals("Unexpected number of test users in the db", 2, db.getUsers().size()); + + _initializer.initialise(db); + + _saslFactory = new CRAMMD5HexServerFactory(); + + _saslServer = _saslFactory.createSaslServer(CRAMMD5HexSaslServer.MECHANISM, + "AMQP", + "localhost", + null, + _initializer.getCallbackHandler()); + assertNotNull("Unable to create saslServer with mechanism type " + CRAMMD5HexSaslServer.MECHANISM, _saslServer); + + } + + public void testSuccessfulAuth() throws Exception + { + + final byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + // Generate client response + final byte[] clientResponse = generateClientResponse("knownuser", "guest", serverChallenge); + + + byte[] nextServerChallenge = _saslServer.evaluateResponse(clientResponse); + assertTrue("Exchange must be flagged as complete after successful authentication", _saslServer.isComplete()); + assertNull("Next server challenge must be null after successful authentication", nextServerChallenge); + + } + + public void testKnownUserPresentsWrongPassword() throws Exception + { + byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + + final byte[] clientResponse = generateClientResponse("knownuser", "wrong!", serverChallenge); + try + { + _saslServer.evaluateResponse(clientResponse); + fail("Exception not thrown"); + } + catch (SaslException se) + { + // PASS + } + assertFalse("Exchange must not be flagged as complete after unsuccessful authentication", _saslServer.isComplete()); + } + + public void testUnknownUser() throws Exception + { + final byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + + final byte[] clientResponse = generateClientResponse("unknownuser", "guest", serverChallenge); + + try + { + _saslServer.evaluateResponse(clientResponse); + fail("Exception not thrown"); + } + catch (SaslException se) + { + assertExceptionHasUnderlyingAsCause(AccountNotFoundException.class, se); + // PASS + } + assertFalse("Exchange must not be flagged as complete after unsuccessful authentication", _saslServer.isComplete()); + } + + /** + * + * Demonstrates QPID-3158. A defect meant that users with some valid password were failing to + * authenticate when using the .NET 0-8 client (uses this SASL mechanism). + * It so happens that password "guest2" was one of the affected passwords. + * + * @throws Exception + */ + public void testSuccessfulAuthReproducingQpid3158() throws Exception + { + byte[] serverChallenge = _saslServer.evaluateResponse(new byte[0]); + + // Generate client response + byte[] resp = generateClientResponse("qpid3158user", "guest2", serverChallenge); + + byte[] nextServerChallenge = _saslServer.evaluateResponse(resp); + assertTrue("Exchange must be flagged as complete after successful authentication", _saslServer.isComplete()); + assertNull("Next server challenge must be null after successful authentication", nextServerChallenge); + } + + /** + * Since we don't have a CRAM-MD5-HEX implementation client implementation in Java, this method + * provides the implementation for first principals. + * + * @param userId user id + * @param clearTextPassword clear text password + * @param serverChallenge challenge from server + * + * @return challenge response + */ + private byte[] generateClientResponse(final String userId, final String clearTextPassword, final byte[] serverChallenge) throws Exception + { + byte[] digestedPasswordBytes = MessageDigest.getInstance("MD5").digest(clearTextPassword.getBytes()); + char[] hexEncodedDigestedPassword = Hex.encodeHex(digestedPasswordBytes); + byte[] hexEncodedDigestedPasswordBytes = new String(hexEncodedDigestedPassword).getBytes(); + + + Mac hmacMd5 = Mac.getInstance("HmacMD5"); + hmacMd5.init(new SecretKeySpec(hexEncodedDigestedPasswordBytes, "HmacMD5")); + final byte[] messageAuthenticationCode = hmacMd5.doFinal(serverChallenge); + + // Build client response + String responseAsString = userId + " " + new String(Hex.encodeHex(messageAuthenticationCode)); + byte[] resp = responseAsString.getBytes(); + return resp; + } + + /** + * Creates a test principal database. + * + * @return + * @throws IOException + */ + private Base64MD5PasswordFilePrincipalDatabase createTestPrincipalDatabase() throws IOException + { + Base64MD5PasswordFilePrincipalDatabase db = new Base64MD5PasswordFilePrincipalDatabase(); + File file = File.createTempFile("passwd", "db"); + file.deleteOnExit(); + db.open(file); + db.createPrincipal( createTestPrincipal("knownuser"), "guest".toCharArray()); + db.createPrincipal( createTestPrincipal("qpid3158user"), "guest2".toCharArray()); + return db; + } + + private Principal createTestPrincipal(final String name) + { + return new Principal() + { + public String getName() + { + return name; + } + }; + } + + private void assertExceptionHasUnderlyingAsCause(final Class expectedUnderlying, Throwable e) + { + assertNotNull(e); + int infiniteLoopGuard = 0; // Guard against loops in the cause chain + boolean foundExpectedUnderlying = false; + while (e.getCause() != null && infiniteLoopGuard++ < 10) + { + if (expectedUnderlying.equals(e.getCause().getClass())) + { + foundExpectedUnderlying = true; + break; + } + e = e.getCause(); + } + + if (!foundExpectedUnderlying) + { + fail("Not found expected underlying exception " + expectedUnderlying + " as underlying cause of " + e.getClass()); + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java new file mode 100644 index 0000000000..f5247634ac --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslServerTestCase.java @@ -0,0 +1,66 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.security.auth.sasl; + +import junit.framework.TestCase; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +public abstract class SaslServerTestCase extends TestCase +{ + protected SaslServer server; + protected String username = "u"; + protected String password = "p"; + protected String notpassword = "a"; + protected PrincipalDatabase db = new TestPrincipalDatabase(); + + protected byte[] correctresponse; + protected byte[] wrongresponse; + + public void testSucessfulAuth() throws SaslException + { + byte[] resp = this.server.evaluateResponse(correctresponse); + assertNull(resp); + } + + public void testFailAuth() + { + boolean exceptionCaught = false; + try + { + byte[] resp = this.server.evaluateResponse(wrongresponse); + } + catch (SaslException e) + { + assertTrue(e.getMessage().contains("Authentication failed")); + exceptionCaught = true; + } + if (!exceptionCaught) + { + fail("Should have thrown SaslException"); + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslUtil.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslUtil.java new file mode 100644 index 0000000000..251ebc4c81 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/SaslUtil.java @@ -0,0 +1,85 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.security.auth.sasl; + +import java.security.MessageDigest; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +public class SaslUtil +{ + + private static byte SEPARATOR = 0; + + public static byte[] generatePlainClientResponse(String userName, String userPassword) throws Exception + { + byte[] password = userPassword.getBytes("UTF8"); + byte user[] = userName.getBytes("UTF8"); + byte response[] = new byte[password.length + user.length + 2]; + int size = 0; + response[size++] = SEPARATOR; + System.arraycopy(user, 0, response, size, user.length); + size += user.length; + response[size++] = SEPARATOR; + System.arraycopy(password, 0, response, size, password.length); + return response; + } + + public static byte[] generateCramMD5HexClientResponse(String userName, String userPassword, byte[] challengeBytes) + throws Exception + { + String macAlgorithm = "HmacMD5"; + byte[] digestedPasswordBytes = MessageDigest.getInstance("MD5").digest(userPassword.getBytes("UTF-8")); + byte[] hexEncodedDigestedPasswordBytes = toHex(digestedPasswordBytes).getBytes("UTF-8"); + Mac mac = Mac.getInstance(macAlgorithm); + mac.init(new SecretKeySpec(hexEncodedDigestedPasswordBytes, macAlgorithm)); + final byte[] messageAuthenticationCode = mac.doFinal(challengeBytes); + String responseAsString = userName + " " + toHex(messageAuthenticationCode); + return responseAsString.getBytes(); + } + + public static byte[] generateCramMD5ClientResponse(String userName, String userPassword, byte[] challengeBytes) + throws Exception + { + String macAlgorithm = "HmacMD5"; + Mac mac = Mac.getInstance(macAlgorithm); + mac.init(new SecretKeySpec(userPassword.getBytes("UTF-8"), macAlgorithm)); + final byte[] messageAuthenticationCode = mac.doFinal(challengeBytes); + String responseAsString = userName + " " + toHex(messageAuthenticationCode); + return responseAsString.getBytes(); + } + + public static String toHex(byte[] data) + { + StringBuffer hash = new StringBuffer(); + for (int i = 0; i < data.length; i++) + { + String hex = Integer.toHexString(0xFF & data[i]); + if (hex.length() == 1) + { + hash.append('0'); + } + hash.append(hex); + } + return hash.toString(); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java new file mode 100644 index 0000000000..17c63d738c --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/TestPrincipalDatabase.java @@ -0,0 +1,107 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.File; +import java.io.IOException; +import java.security.Principal; +import java.util.List; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +public class TestPrincipalDatabase implements PrincipalDatabase +{ + + public boolean createPrincipal(Principal principal, char[] password) + { + // TODO Auto-generated method stub + return false; + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public Principal getUser(String username) + { + // TODO Auto-generated method stub + return null; + } + + public List getUsers() + { + // TODO Auto-generated method stub + return null; + } + + public void setPassword(Principal principal, PasswordCallback callback) throws IOException, + AccountNotFoundException + { + callback.setPassword("p".toCharArray()); + } + + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + // TODO Auto-generated method stub + return false; + } + + public void reload() throws IOException + { + // TODO Auto-generated method stub + } + + @Override + public void open(File passwordFile) throws IOException + { + // TODO Auto-generated method stub + } + + @Override + public String getMechanisms() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public SaslServer createSaslServer(String mechanism, String localFQDN, + Principal externalPrincipal) throws SaslException + { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java new file mode 100644 index 0000000000..6245064bf7 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/amqplain/AMQPlainSaslServerTest.java @@ -0,0 +1,43 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.security.auth.sasl.amqplain; + +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableFactory; +import org.apache.qpid.server.security.auth.sasl.SaslServerTestCase; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class AMQPlainSaslServerTest extends SaslServerTestCase +{ + protected void setUp() throws Exception + { + UsernamePasswordInitialiser handler = new AmqPlainInitialiser(); + handler.initialise(db); + this.server = new AmqPlainSaslServer(handler.getCallbackHandler()); + FieldTable table = FieldTableFactory.newFieldTable(); + table.setString("LOGIN", username); + table.setString("PASSWORD", password); + correctresponse = table.getDataAsBytes(); + table.setString("PASSWORD", notpassword); + wrongresponse = table.getDataAsBytes(); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java new file mode 100644 index 0000000000..5dd51250dc --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerTest.java @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.qpid.server.security.auth.sasl.plain; + +import org.apache.qpid.server.security.auth.sasl.SaslServerTestCase; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class PlainSaslServerTest extends SaslServerTestCase +{ + + protected void setUp() throws Exception + { + UsernamePasswordInitialiser handler = new PlainInitialiser(); + handler.initialise(db); + this.server = new PlainSaslServer(handler.getCallbackHandler()); + correctresponse = new byte[]{0x0, (byte) username.charAt(0), 0x0, (byte) password.charAt(0)}; + wrongresponse = new byte[]{0x0,(byte) username.charAt(0), 0x0, (byte) notpassword.charAt(0)}; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupDatabaseTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupDatabaseTest.java new file mode 100644 index 0000000000..b020c1655a --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupDatabaseTest.java @@ -0,0 +1,456 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.group; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.Set; + +import org.apache.qpid.server.security.group.FileGroupDatabase; + +import junit.framework.TestCase; + +public class FileGroupDatabaseTest extends TestCase +{ + private static final String USER1 = "user1"; + private static final String USER2 = "user2"; + private static final String USER3 = "user3"; + + private static final String MY_GROUP = "myGroup"; + private static final String MY_GROUP2 = "myGroup2"; + private static final String MY_GROUP1 = "myGroup1"; + + private FileGroupDatabase _groupDatabase = new FileGroupDatabase(); + private String _groupFile; + + public void testGetAllGroups() throws Exception + { + writeAndSetGroupFile("myGroup.users", USER1); + + Set groups = _groupDatabase.getAllGroups(); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP)); + } + + public void testGetAllGroupsWhenGroupFileEmpty() throws Exception + { + _groupDatabase.setGroupFile(_groupFile); + + Set groups = _groupDatabase.getAllGroups(); + assertEquals(0, groups.size()); + } + + public void testMissingGroupFile() throws Exception + { + try + { + _groupDatabase.setGroupFile("/not/a/file"); + fail("Exception not thrown"); + } + catch (FileNotFoundException fnfe) + { + // PASS + } + } + + public void testInvalidFormat() throws Exception + { + writeGroupFile("name.notvalid", USER1); + + try + { + _groupDatabase.setGroupFile(_groupFile); + fail("Exception not thrown"); + } + catch (IllegalArgumentException gde) + { + // PASS + } + } + + public void testGetUsersInGroup() throws Exception + { + writeGroupFile("myGroup.users", "user1,user2,user3"); + + _groupDatabase.setGroupFile(_groupFile); + + Set users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(3, users.size()); + } + + public void testDuplicateUsersInGroupAreConflated() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user1,user3,user1"); + + Set users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(2, users.size()); + } + + public void testGetUsersWithEmptyGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", ""); + + Set users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertTrue(users.isEmpty()); + } + + public void testGetUsersInNonExistentGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2,user3"); + + Set users = _groupDatabase.getUsersInGroup("groupDoesntExist"); + assertNotNull(users); + assertTrue(users.isEmpty()); + } + + public void testGetUsersInNullGroup() throws Exception + { + writeAndSetGroupFile(); + assertTrue(_groupDatabase.getUsersInGroup(null).isEmpty()); + } + + public void testGetGroupPrincipalsForUserWhenUserBelongsToOneGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + Set groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP)); + } + + public void testGetGroupPrincipalsForUserWhenUserBelongsToTwoGroup() throws Exception + { + writeAndSetGroupFile("myGroup1.users", "user1,user2", + "myGroup2.users", "user1,user3"); + Set groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(2, groups.size()); + assertTrue(groups.contains(MY_GROUP1)); + assertTrue(groups.contains(MY_GROUP2)); + } + + public void testGetGroupPrincipalsForUserWhenUserAddedToGroup() throws Exception + { + writeAndSetGroupFile("myGroup1.users", "user1,user2", + "myGroup2.users", USER2); + Set groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP1)); + + _groupDatabase.addUserToGroup(USER1, MY_GROUP2); + + groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(2, groups.size()); + assertTrue(groups.contains(MY_GROUP1)); + assertTrue(groups.contains(MY_GROUP2)); + + Set users = _groupDatabase.getUsersInGroup(MY_GROUP2); + assertEquals(2, users.size()); + assertTrue(users.contains(USER1)); + assertTrue(users.contains(USER2)); + } + + public void testGetGroupPrincipalsForUserWhenUserRemovedFromGroup() throws Exception + { + writeAndSetGroupFile("myGroup1.users", "user1,user2", + "myGroup2.users", "user1,user2"); + Set groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(2, groups.size()); + assertTrue(groups.contains(MY_GROUP1)); + assertTrue(groups.contains(MY_GROUP2)); + + _groupDatabase.removeUserFromGroup(USER1, MY_GROUP2); + + groups = _groupDatabase.getGroupsForUser(USER1); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP1)); + } + + public void testGetGroupPrincipalsForUserWhenUserAdddedToGroupTheyAreAlreadyIn() throws Exception + { + writeAndSetGroupFile("myGroup.users", USER1); + _groupDatabase.addUserToGroup(USER1, MY_GROUP); + + Set groups = _groupDatabase.getGroupsForUser(USER1); + + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP)); + + Set users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertEquals(1, users.size()); + assertTrue(users.contains(USER1)); + } + + public void testGetGroupPrincipalsForUserWhenUserNotKnown() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + Set groups = _groupDatabase.getGroupsForUser(USER3); + assertEquals(0, groups.size()); + } + + public void testGetGroupPrincipalsForNullUser() throws Exception + { + writeAndSetGroupFile(); + assertTrue(_groupDatabase.getGroupsForUser(null).isEmpty()); + } + + public void testAddUserToExistingGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + + Set users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(2, users.size()); + + _groupDatabase.addUserToGroup(USER3, MY_GROUP); + + users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(3, users.size()); + } + + public void testAddUserToEmptyGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", ""); + + Set users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(0, users.size()); + + _groupDatabase.addUserToGroup(USER3, MY_GROUP); + + users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(1, users.size()); + } + + public void testAddUserToNonExistentGroup() throws Exception + { + writeAndSetGroupFile(); + + Set users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(0, users.size()); + + try + { + _groupDatabase.addUserToGroup(USER3, MY_GROUP); + fail("Expected exception not thrown"); + } + catch(IllegalArgumentException e) + { + // pass + } + + users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(0, users.size()); + } + + public void testRemoveUserFromExistingGroup() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + + Set users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(2, users.size()); + + _groupDatabase.removeUserFromGroup(USER2, MY_GROUP); + + users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertNotNull(users); + assertEquals(1, users.size()); + } + + public void testRemoveUserFromNonexistentGroup() throws Exception + { + writeAndSetGroupFile(); + + try + { + _groupDatabase.removeUserFromGroup(USER1, MY_GROUP); + fail("Expected exception not thrown"); + } + catch(IllegalArgumentException e) + { + // pass + } + + assertTrue(_groupDatabase.getUsersInGroup(MY_GROUP).isEmpty()); + } + + public void testRemoveUserFromGroupTwice() throws Exception + { + writeAndSetGroupFile("myGroup.users", USER1); + assertTrue(_groupDatabase.getUsersInGroup(MY_GROUP).contains(USER1)); + + _groupDatabase.removeUserFromGroup(USER1, MY_GROUP); + assertTrue(_groupDatabase.getUsersInGroup(MY_GROUP).isEmpty()); + + _groupDatabase.removeUserFromGroup(USER1, MY_GROUP); + assertTrue(_groupDatabase.getUsersInGroup(MY_GROUP).isEmpty()); + } + + public void testAddUserPersistedToFile() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + + Set users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertEquals(2, users.size()); + + _groupDatabase.addUserToGroup(USER3, MY_GROUP); + assertEquals(3, users.size()); + + FileGroupDatabase newGroupDatabase = new FileGroupDatabase(); + newGroupDatabase.setGroupFile(_groupFile); + + Set newUsers = newGroupDatabase.getUsersInGroup(MY_GROUP); + assertEquals(users.size(), newUsers.size()); + } + + public void testRemoveUserPersistedToFile() throws Exception + { + writeAndSetGroupFile("myGroup.users", "user1,user2"); + + Set users = _groupDatabase.getUsersInGroup(MY_GROUP); + assertEquals(2, users.size()); + + _groupDatabase.removeUserFromGroup(USER2, MY_GROUP); + assertEquals(1, users.size()); + + FileGroupDatabase newGroupDatabase = new FileGroupDatabase(); + newGroupDatabase.setGroupFile(_groupFile); + + Set newUsers = newGroupDatabase.getUsersInGroup(MY_GROUP); + assertEquals(users.size(), newUsers.size()); + } + + public void testCreateGroupPersistedToFile() throws Exception + { + writeAndSetGroupFile(); + + Set groups = _groupDatabase.getAllGroups(); + assertEquals(0, groups.size()); + + _groupDatabase.createGroup(MY_GROUP); + + groups = _groupDatabase.getAllGroups(); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP)); + + FileGroupDatabase newGroupDatabase = new FileGroupDatabase(); + newGroupDatabase.setGroupFile(_groupFile); + + Set newGroups = newGroupDatabase.getAllGroups(); + assertEquals(1, newGroups.size()); + assertTrue(newGroups.contains(MY_GROUP)); + } + + public void testRemoveGroupPersistedToFile() throws Exception + { + writeAndSetGroupFile("myGroup1.users", "user1,user2", + "myGroup2.users", "user1,user2"); + + Set groups = _groupDatabase.getAllGroups(); + assertEquals(2, groups.size()); + + Set groupsForUser1 = _groupDatabase.getGroupsForUser(USER1); + assertEquals(2, groupsForUser1.size()); + + _groupDatabase.removeGroup(MY_GROUP1); + + groups = _groupDatabase.getAllGroups(); + assertEquals(1, groups.size()); + assertTrue(groups.contains(MY_GROUP2)); + + groupsForUser1 = _groupDatabase.getGroupsForUser(USER1); + assertEquals(1, groupsForUser1.size()); + + FileGroupDatabase newGroupDatabase = new FileGroupDatabase(); + newGroupDatabase.setGroupFile(_groupFile); + + Set newGroups = newGroupDatabase.getAllGroups(); + assertEquals(1, newGroups.size()); + assertTrue(newGroups.contains(MY_GROUP2)); + + Set newGroupsForUser1 = newGroupDatabase.getGroupsForUser(USER1); + assertEquals(1, newGroupsForUser1.size()); + assertTrue(newGroupsForUser1.contains(MY_GROUP2)); +} + + @Override + protected void setUp() throws Exception + { + super.setUp(); + _groupFile = createEmptyTestGroupFile(); + } + + private void writeAndSetGroupFile(String... groupAndUsers) throws Exception + { + writeGroupFile(groupAndUsers); + _groupDatabase.setGroupFile(_groupFile); + } + + private void writeGroupFile(String... groupAndUsers) throws Exception + { + if (groupAndUsers.length % 2 != 0) + { + throw new IllegalArgumentException("Number of groupAndUsers must be even"); + } + + Properties props = new Properties(); + for (int i = 0 ; i < groupAndUsers.length; i=i+2) + { + String group = groupAndUsers[i]; + String users = groupAndUsers[i+1]; + props.put(group, users); + } + + props.store(new FileOutputStream(_groupFile), "test group file"); + } + + private String createEmptyTestGroupFile() throws IOException + { + File tmpGroupFile = File.createTempFile("groups", "grp"); + tmpGroupFile.deleteOnExit(); + + return tmpGroupFile.getAbsolutePath(); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + + if (_groupFile != null) + { + File groupFile = new File(_groupFile); + if (groupFile.exists()) + { + groupFile.delete(); + } + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerFactoryTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerFactoryTest.java new file mode 100644 index 0000000000..90308d316b --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerFactoryTest.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.security.group; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.qpid.server.model.GroupProvider; +import org.apache.qpid.test.utils.TestFileUtils; + +public class FileGroupManagerFactoryTest extends TestCase +{ + + private FileGroupManagerFactory _factory = new FileGroupManagerFactory(); + private Map _configuration = new HashMap(); + private String _emptyButValidGroupFile = TestFileUtils.createTempFile(this).getAbsolutePath(); + + public void testInstanceCreated() throws Exception + { + _configuration.put(GroupProvider.TYPE, FileGroupManagerFactory.GROUP_FILE_PROVIDER_TYPE); + _configuration.put(FileGroupManagerFactory.PATH, _emptyButValidGroupFile); + + GroupManager manager = _factory.createInstance(_configuration); + assertNotNull(manager); + assertTrue(manager instanceof FileGroupManager); + } + + public void testReturnsNullWhenNoConfig() throws Exception + { + GroupManager manager = _factory.createInstance(_configuration); + assertNull(manager); + } + + public void testReturnsNullWhenConfigNotForThisPlugin() throws Exception + { + _configuration.put(GroupProvider.TYPE, "other-group-manager"); + + GroupManager manager = _factory.createInstance(_configuration); + assertNull(manager); + } + + + public void testRejectsConfigThatIsMissingAttributeValue() throws Exception + { + _configuration.put(GroupProvider.TYPE, FileGroupManagerFactory.GROUP_FILE_PROVIDER_TYPE); + _configuration.put(FileGroupManagerFactory.PATH, null); + + try + { + _factory.createInstance(_configuration); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerTest.java new file mode 100644 index 0000000000..152703d548 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/FileGroupManagerTest.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.group; + +import java.io.File; +import java.io.FileOutputStream; +import java.security.Principal; +import java.util.Properties; +import java.util.Set; + +import org.apache.qpid.server.security.auth.UsernamePrincipal; +import org.apache.qpid.test.utils.QpidTestCase; + +public class FileGroupManagerTest extends QpidTestCase +{ + private static final String MYGROUP_USERS = "user1"; + private static final String MY_GROUP = "myGroup.users"; + private static final String MY_GROUP2 = "myGroup2.users"; + private File _tmpGroupFile; + private FileGroupManager _manager; + + @Override + public void tearDown() throws Exception + { + super.tearDown(); + + if (_tmpGroupFile != null) + { + if (_tmpGroupFile.exists()) + { + _tmpGroupFile.delete(); + } + } + } + + public void testValidGroupFile() throws Exception + { + final String groupFileName = writeGroupFile(); + + _manager = new FileGroupManager(groupFileName); + assertNotNull(_manager); + } + + public void testNonExistentGroupFile() throws Exception + { + final String filePath = TMP_FOLDER + File.separator + "non.existing"; + File file = new File(filePath); + if (file.exists()) + { + file.delete(); + } + assertFalse("File should not exist", file.exists()); + try + { + _manager = new FileGroupManager(filePath); + assertFalse("File should be created", file.exists()); + _manager.onCreate(); + assertTrue("File should be created", file.exists()); + _manager.open(); + Set groups = _manager.getGroupPrincipals(); + assertTrue("No group should exist", groups.isEmpty()); + } + finally + { + file.delete(); + } + } + + public void testGetGroupPrincipalsForUser() throws Exception + { + final String groupFileName = writeGroupFile(); + _manager = new FileGroupManager(groupFileName); + _manager.open(); + Set principals = _manager.getGroupPrincipalsForUser("user1"); + assertEquals(1, principals.size()); + assertTrue(principals.contains(new GroupPrincipal("myGroup"))); + } + + public void testGetUserPrincipalsForGroup() throws Exception + { + final String groupFileName = writeGroupFile(); + _manager = new FileGroupManager(groupFileName); + _manager.open(); + Set principals = _manager.getUserPrincipalsForGroup("myGroup"); + assertEquals(1, principals.size()); + assertTrue(principals.contains(new UsernamePrincipal("user1"))); + } + + public void testGetGroupPrincipals() throws Exception + { + final String groupFileName = writeGroupFile(MY_GROUP, MYGROUP_USERS, MY_GROUP2, MYGROUP_USERS); + _manager = new FileGroupManager(groupFileName); + _manager.open(); + Set principals = _manager.getGroupPrincipals(); + assertEquals(2, principals.size()); + assertTrue(principals.contains(new GroupPrincipal("myGroup"))); + assertTrue(principals.contains(new GroupPrincipal("myGroup2"))); + } + + public void testCreateGroup() throws Exception + { + final String groupFileName = writeGroupFile(); + _manager = new FileGroupManager(groupFileName); + _manager.open(); + Set principals = _manager.getGroupPrincipals(); + assertEquals(1, principals.size()); + + _manager.createGroup("myGroup2"); + + principals = _manager.getGroupPrincipals(); + assertEquals(2, principals.size()); + assertTrue(principals.contains(new GroupPrincipal("myGroup2"))); + } + + public void testRemoveGroup() throws Exception + { + final String groupFileName = writeGroupFile(MY_GROUP, MYGROUP_USERS); + _manager = new FileGroupManager(groupFileName); + _manager.open(); + Set principals = _manager.getGroupPrincipals(); + assertEquals(1, principals.size()); + + _manager.removeGroup("myGroup"); + + principals = _manager.getGroupPrincipals(); + assertEquals(0, principals.size()); + } + + public void testAddUserToGroup() throws Exception + { + final String groupFileName = writeGroupFile(MY_GROUP, MYGROUP_USERS); + _manager = new FileGroupManager(groupFileName); + _manager.open(); + Set principals = _manager.getUserPrincipalsForGroup("myGroup"); + assertEquals(1, principals.size()); + assertFalse(principals.contains(new UsernamePrincipal("user2"))); + + _manager.addUserToGroup("user2", "myGroup"); + + principals = _manager.getUserPrincipalsForGroup("myGroup"); + assertEquals(2, principals.size()); + assertTrue(principals.contains(new UsernamePrincipal("user2"))); + } + + public void testRemoveUserInGroup() throws Exception + { + final String groupFileName = writeGroupFile(MY_GROUP, MYGROUP_USERS); + _manager = new FileGroupManager(groupFileName); + _manager.open(); + Set principals = _manager.getUserPrincipalsForGroup("myGroup"); + assertEquals(1, principals.size()); + assertTrue(principals.contains(new UsernamePrincipal("user1"))); + + _manager.removeUserFromGroup("user1", "myGroup"); + + principals = _manager.getUserPrincipalsForGroup("myGroup"); + assertEquals(0, principals.size()); + } + + private String writeGroupFile() throws Exception + { + return writeGroupFile(MY_GROUP, MYGROUP_USERS); + } + + private String writeGroupFile(String... groupAndUsers) throws Exception + { + if (groupAndUsers.length % 2 != 0) + { + throw new IllegalArgumentException("Number of groupAndUsers must be even"); + } + + _tmpGroupFile = File.createTempFile("groups", "grp"); + _tmpGroupFile.deleteOnExit(); + + Properties props = new Properties(); + for (int i = 0 ; i < groupAndUsers.length; i=i+2) + { + String group = groupAndUsers[i]; + String users = groupAndUsers[i+1]; + props.put(group, users); + } + + props.store(new FileOutputStream(_tmpGroupFile), "test group file"); + + return _tmpGroupFile.getCanonicalPath(); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/GroupPrincipalTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/GroupPrincipalTest.java new file mode 100644 index 0000000000..d285a0797a --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/security/group/GroupPrincipalTest.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.security.group; + +import org.apache.qpid.server.security.auth.UsernamePrincipal; + +import junit.framework.TestCase; + +public class GroupPrincipalTest extends TestCase +{ + public void testGetName() + { + final GroupPrincipal principal = new GroupPrincipal("group"); + assertEquals("group", principal.getName()); + } + + public void testAddRejected() + { + final GroupPrincipal principal = new GroupPrincipal("group"); + final UsernamePrincipal user = new UsernamePrincipal("name"); + + try + { + principal.addMember(user); + fail("Exception not thrown"); + } + catch (UnsupportedOperationException uso) + { + // PASS + } + } + + public void testEqualitySameName() + { + final String string = "string"; + final GroupPrincipal principal1 = new GroupPrincipal(string); + final GroupPrincipal principal2 = new GroupPrincipal(string); + assertTrue(principal1.equals(principal2)); + } + + public void testEqualityEqualName() + { + final GroupPrincipal principal1 = new GroupPrincipal(new String("string")); + final GroupPrincipal principal2 = new GroupPrincipal(new String("string")); + assertTrue(principal1.equals(principal2)); + } + + public void testInequalityDifferentGroupPrincipals() + { + GroupPrincipal principal1 = new GroupPrincipal("string1"); + GroupPrincipal principal2 = new GroupPrincipal("string2"); + assertFalse(principal1.equals(principal2)); + } + + public void testInequalityNonGroupPrincipal() + { + GroupPrincipal principal = new GroupPrincipal("string"); + assertFalse(principal.equals(new UsernamePrincipal("string"))); + } + + public void testInequalityNull() + { + GroupPrincipal principal = new GroupPrincipal("string"); + assertFalse(principal.equals(null)); + } + + + + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/stats/StatisticsCounterTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/stats/StatisticsCounterTest.java new file mode 100644 index 0000000000..147879f5e8 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/stats/StatisticsCounterTest.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.stats; + +import junit.framework.TestCase; + +/** + * Unit tests for the {@link StatisticsCounter} class. + */ +public class StatisticsCounterTest extends TestCase +{ + /** + * Check that statistics counters are created correctly. + */ + public void testCreate() + { + long before = System.currentTimeMillis(); + StatisticsCounter counter = new StatisticsCounter("name", 1234L); + long after = System.currentTimeMillis(); + + assertTrue(before <= counter.getStart()); + assertTrue(after >= counter.getStart()); + assertTrue(counter.getName().startsWith("name-")); + assertEquals(1234L, counter.getPeriod()); + } + + /** + * Check that totals add up correctly. + */ + public void testTotal() + { + StatisticsCounter counter = new StatisticsCounter("test", 1000L); + long start = counter.getStart(); + for (int i = 0; i < 100; i++) + { + counter.registerEvent(i, start + i); + } + assertEquals(99 * 50, counter.getTotal()); // cf. Gauss + } + + /** + * Test totals add up correctly even when messages are delivered + * out-of-order. + */ + public void testTotalOutOfOrder() + { + StatisticsCounter counter = new StatisticsCounter("test", 1000L); + long start = counter.getStart(); + assertEquals(0, counter.getTotal()); + counter.registerEvent(10, start + 2500); + assertEquals(10, counter.getTotal()); + counter.registerEvent(20, start + 1500); + assertEquals(30, counter.getTotal()); + counter.registerEvent(10, start + 500); + assertEquals(40, counter.getTotal()); + } + + /** + * Test that the peak rate is reported correctly. + */ + public void testPeak() throws Exception + { + StatisticsCounter counter = new StatisticsCounter("test", 1000L); + long start = counter.getStart(); + assertEquals(0.0, counter.getPeak()); + Thread.sleep(500); + counter.registerEvent(1000, start + 500); + Thread.sleep(1000); + assertEquals(1000.0, counter.getPeak()); + counter.registerEvent(2000, start + 1500); + Thread.sleep(1000); + assertEquals(2000.0, counter.getPeak()); + counter.registerEvent(1000, start + 2500); + Thread.sleep(1000); + assertEquals(2000.0, counter.getPeak()); + } + + /** + * Test that peak rate is reported correctly for out-of-order messages, + * and the total is also unaffected. + */ + public void testPeakOutOfOrder() throws Exception + { + StatisticsCounter counter = new StatisticsCounter("test", 1000L); + long start = counter.getStart(); + assertEquals(0.0, counter.getPeak()); + counter.registerEvent(1000, start + 2500); + Thread.sleep(1500); + assertEquals(0.0, counter.getPeak()); + counter.registerEvent(2000, start + 1500); + + // make sure, that getPeak invocation occurs at "start + 2500" + // if test thread over-sleeps for 500+ mls + // the peak value can be incremented and test will fail + long sleep = start + 2500 - System.currentTimeMillis(); + Thread.sleep(sleep < 0 ? 0 : sleep); + assertEquals(0.0, counter.getPeak()); + counter.registerEvent(1000, start + 500); + Thread.sleep(1500); + assertEquals(4000.0, counter.getPeak()); + Thread.sleep(2000); + assertEquals(4000.0, counter.getPeak()); + counter.registerEvent(1000, start + 500); + assertEquals(4000.0, counter.getPeak()); + Thread.sleep(2000); + counter.registerEvent(1000); + assertEquals(4000.0, counter.getPeak()); + assertEquals(6000, counter.getTotal()); + } + + /** + * Test the current rate is generated correctly. + */ + public void testRate() throws Exception + { + StatisticsCounter counter = new StatisticsCounter("test", 1000L); + assertEquals(0.0, counter.getRate()); + Thread.sleep(500); + counter.registerEvent(1000); + Thread.sleep(1000); + assertEquals(1000.0, counter.getRate()); + counter.registerEvent(2000); + Thread.sleep(1000); + assertEquals(2000.0, counter.getRate()); + counter.registerEvent(1000); + Thread.sleep(1000); + assertEquals(1000.0, counter.getRate()); + Thread.sleep(1000); + assertEquals(0.0, counter.getRate()); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/AbstractDurableConfigurationStoreTestCase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/AbstractDurableConfigurationStoreTestCase.java new file mode 100644 index 0000000000..fd8148f2ce --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/AbstractDurableConfigurationStoreTestCase.java @@ -0,0 +1,520 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.times; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.model.LifetimePolicy; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.store.MessageStoreRecoveryHandler.StoredMessageRecoveryHandler; +import org.apache.qpid.server.store.Transaction.Record; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.util.FileUtils; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public abstract class AbstractDurableConfigurationStoreTestCase extends QpidTestCase +{ + private static final String EXCHANGE_NAME = "exchangeName"; + + private static final String EXCHANGE = org.apache.qpid.server.model.Exchange.class.getSimpleName(); + private static final String BINDING = org.apache.qpid.server.model.Binding.class.getSimpleName(); + private static final String QUEUE = Queue.class.getSimpleName(); + + private String _storePath; + private String _storeName; + private MessageStore _messageStore; + private Configuration _configuration; + private VirtualHost _virtualHost; + + private ConfigurationRecoveryHandler _recoveryHandler; + private MessageStoreRecoveryHandler _messageStoreRecoveryHandler; + private StoredMessageRecoveryHandler _storedMessageRecoveryHandler; + private TransactionLogRecoveryHandler _logRecoveryHandler; + private TransactionLogRecoveryHandler.QueueEntryRecoveryHandler _queueEntryRecoveryHandler; + private TransactionLogRecoveryHandler.DtxRecordRecoveryHandler _dtxRecordRecoveryHandler; + + private Exchange _exchange = mock(Exchange.class); + private static final String ROUTING_KEY = "routingKey"; + private static final String QUEUE_NAME = "queueName"; + private Map _bindingArgs; + private UUID _queueId; + private UUID _exchangeId; + private DurableConfigurationStore _configStore; + + public void setUp() throws Exception + { + super.setUp(); + + _queueId = UUIDGenerator.generateRandomUUID(); + _exchangeId = UUIDGenerator.generateRandomUUID(); + + _storeName = getName(); + _storePath = TMP_FOLDER + File.separator + _storeName; + FileUtils.delete(new File(_storePath), true); + setTestSystemProperty("QPID_WORK", TMP_FOLDER); + _configuration = mock(Configuration.class); + _recoveryHandler = mock(ConfigurationRecoveryHandler.class); + _storedMessageRecoveryHandler = mock(StoredMessageRecoveryHandler.class); + _logRecoveryHandler = mock(TransactionLogRecoveryHandler.class); + _messageStoreRecoveryHandler = mock(MessageStoreRecoveryHandler.class); + _queueEntryRecoveryHandler = mock(TransactionLogRecoveryHandler.QueueEntryRecoveryHandler.class); + _dtxRecordRecoveryHandler = mock(TransactionLogRecoveryHandler.DtxRecordRecoveryHandler.class); + _virtualHost = mock(VirtualHost.class); + + when(_messageStoreRecoveryHandler.begin()).thenReturn(_storedMessageRecoveryHandler); + when(_logRecoveryHandler.begin(any(MessageStore.class))).thenReturn(_queueEntryRecoveryHandler); + when(_queueEntryRecoveryHandler.completeQueueEntryRecovery()).thenReturn(_dtxRecordRecoveryHandler); + when(_exchange.getName()).thenReturn(EXCHANGE_NAME); + + when(_exchange.getId()).thenReturn(_exchangeId); + when(_configuration.getString(eq(MessageStoreConstants.ENVIRONMENT_PATH_PROPERTY), anyString())).thenReturn( + _storePath); + when(_virtualHost.getAttribute(eq(VirtualHost.STORE_PATH))).thenReturn(_storePath); + + _bindingArgs = new HashMap(); + String argKey = AMQPFilterTypes.JMS_SELECTOR.toString(); + String argValue = "some selector expression"; + _bindingArgs.put(argKey, argValue); + + reopenStore(); + } + + public void tearDown() throws Exception + { + try + { + closeMessageStore(); + closeConfigStore(); + FileUtils.delete(new File(_storePath), true); + } + finally + { + super.tearDown(); + } + } + + public void testCreateExchange() throws Exception + { + Exchange exchange = createTestExchange(); + DurableConfigurationStoreHelper.createExchange(_configStore, exchange); + + reopenStore(); + verify(_recoveryHandler).configuredObject(eq(_exchangeId), eq(EXCHANGE), + eq(map( org.apache.qpid.server.model.Exchange.NAME, getName(), + org.apache.qpid.server.model.Exchange.TYPE, getName()+"Type", + org.apache.qpid.server.model.Exchange.LIFETIME_POLICY, LifetimePolicy.AUTO_DELETE.toString()))); + } + + private Map map(Object... vals) + { + Map map = new HashMap(); + boolean isValue = false; + String key = null; + for(Object obj : vals) + { + if(isValue) + { + map.put(key,obj); + } + else + { + key = (String) obj; + } + isValue = !isValue; + } + return map; + } + + public void testRemoveExchange() throws Exception + { + Exchange exchange = createTestExchange(); + DurableConfigurationStoreHelper.createExchange(_configStore, exchange); + + DurableConfigurationStoreHelper.removeExchange(_configStore, exchange); + + reopenStore(); + verify(_recoveryHandler, never()).configuredObject(any(UUID.class), anyString(), anyMap()); + } + + public void testBindQueue() throws Exception + { + AMQQueue queue = createTestQueue(QUEUE_NAME, "queueOwner", false, null); + Binding binding = new Binding(UUIDGenerator.generateRandomUUID(), ROUTING_KEY, queue, + _exchange, _bindingArgs); + DurableConfigurationStoreHelper.createBinding(_configStore, binding); + + reopenStore(); + + Map map = new HashMap(); + map.put(org.apache.qpid.server.model.Binding.EXCHANGE, _exchange.getId().toString()); + map.put(org.apache.qpid.server.model.Binding.QUEUE, queue.getId().toString()); + map.put(org.apache.qpid.server.model.Binding.NAME, ROUTING_KEY); + map.put(org.apache.qpid.server.model.Binding.ARGUMENTS,_bindingArgs); + + verify(_recoveryHandler).configuredObject(eq(binding.getId()), eq(BINDING), + eq(map)); + } + + public void testUnbindQueue() throws Exception + { + AMQQueue queue = createTestQueue(QUEUE_NAME, "queueOwner", false, null); + Binding binding = new Binding(UUIDGenerator.generateRandomUUID(), ROUTING_KEY, queue, + _exchange, _bindingArgs); + DurableConfigurationStoreHelper.createBinding(_configStore, binding); + + DurableConfigurationStoreHelper.removeBinding(_configStore, binding); + reopenStore(); + + verify(_recoveryHandler, never()).configuredObject(any(UUID.class), + eq(BINDING), + anyMap()); + } + + public void testCreateQueueAMQQueue() throws Exception + { + AMQQueue queue = createTestQueue(getName(), getName() + "Owner", true, null); + DurableConfigurationStoreHelper.createQueue(_configStore, queue); + + reopenStore(); + Map queueAttributes = new HashMap(); + queueAttributes.put(Queue.NAME, getName()); + queueAttributes.put(Queue.OWNER, getName()+"Owner"); + queueAttributes.put(Queue.EXCLUSIVE, Boolean.TRUE); + verify(_recoveryHandler).configuredObject(eq(_queueId), eq(QUEUE), eq(queueAttributes)); + } + + public void testCreateQueueAMQQueueFieldTable() throws Exception + { + Map attributes = new HashMap(); + attributes.put(Queue.CREATE_DLQ_ON_CREATION, Boolean.TRUE); + attributes.put(Queue.MAXIMUM_DELIVERY_ATTEMPTS, 10); + AMQQueue queue = createTestQueue(getName(), getName() + "Owner", true, attributes); + + DurableConfigurationStoreHelper.createQueue(_configStore, queue); + + reopenStore(); + + + Map queueAttributes = new HashMap(); + + queueAttributes.put(Queue.NAME, getName()); + queueAttributes.put(Queue.OWNER, getName()+"Owner"); + queueAttributes.put(Queue.EXCLUSIVE, Boolean.TRUE); + queueAttributes.putAll(attributes); + + verify(_recoveryHandler).configuredObject(eq(_queueId), eq(QUEUE), eq(queueAttributes)); + } + + public void testCreateQueueAMQQueueWithAlternateExchange() throws Exception + { + Exchange alternateExchange = createTestAlternateExchange(); + + AMQQueue queue = createTestQueue(getName(), getName() + "Owner", true, alternateExchange, null); + DurableConfigurationStoreHelper.createQueue(_configStore, queue); + + reopenStore(); + + Map queueAttributes = new HashMap(); + queueAttributes.put(Queue.NAME, getName()); + queueAttributes.put(Queue.OWNER, getName()+"Owner"); + queueAttributes.put(Queue.EXCLUSIVE, Boolean.TRUE); + queueAttributes.put(Queue.ALTERNATE_EXCHANGE, alternateExchange.getId().toString()); + + verify(_recoveryHandler).configuredObject(eq(_queueId), eq(QUEUE), eq(queueAttributes)); + } + + private Exchange createTestAlternateExchange() + { + UUID exchUuid = UUID.randomUUID(); + Exchange alternateExchange = mock(Exchange.class); + when(alternateExchange.getId()).thenReturn(exchUuid); + return alternateExchange; + } + + public void testUpdateQueueExclusivity() throws Exception + { + // create queue + Map attributes = new HashMap(); + attributes.put(Queue.CREATE_DLQ_ON_CREATION, Boolean.TRUE); + attributes.put(Queue.MAXIMUM_DELIVERY_ATTEMPTS, 10); + AMQQueue queue = createTestQueue(getName(), getName() + "Owner", true, attributes); + + DurableConfigurationStoreHelper.createQueue(_configStore, queue); + + // update the queue to have exclusive=false + queue = createTestQueue(getName(), getName() + "Owner", false, attributes); + + DurableConfigurationStoreHelper.updateQueue(_configStore, queue); + + reopenStore(); + + Map queueAttributes = new HashMap(); + + queueAttributes.put(Queue.NAME, getName()); + queueAttributes.put(Queue.OWNER, getName()+"Owner"); + queueAttributes.put(Queue.EXCLUSIVE, Boolean.FALSE); + queueAttributes.putAll(attributes); + + verify(_recoveryHandler).configuredObject(eq(_queueId), eq(QUEUE), eq(queueAttributes)); + + } + + public void testUpdateQueueAlternateExchange() throws Exception + { + // create queue + Map attributes = new HashMap(); + attributes.put(Queue.CREATE_DLQ_ON_CREATION, Boolean.TRUE); + attributes.put(Queue.MAXIMUM_DELIVERY_ATTEMPTS, 10); + AMQQueue queue = createTestQueue(getName(), getName() + "Owner", true, attributes); + DurableConfigurationStoreHelper.createQueue(_configStore, queue); + + // update the queue to have exclusive=false + Exchange alternateExchange = createTestAlternateExchange(); + queue = createTestQueue(getName(), getName() + "Owner", false, alternateExchange, attributes); + + DurableConfigurationStoreHelper.updateQueue(_configStore, queue); + + reopenStore(); + + Map queueAttributes = new HashMap(); + + queueAttributes.put(Queue.NAME, getName()); + queueAttributes.put(Queue.OWNER, getName()+"Owner"); + queueAttributes.put(Queue.EXCLUSIVE, Boolean.FALSE); + queueAttributes.putAll(attributes); + queueAttributes.put(Queue.ALTERNATE_EXCHANGE, alternateExchange.getId().toString()); + + verify(_recoveryHandler).configuredObject(eq(_queueId), eq(QUEUE), eq(queueAttributes)); + } + + public void testRemoveQueue() throws Exception + { + // create queue + Map attributes = new HashMap(); + attributes.put(Queue.CREATE_DLQ_ON_CREATION, Boolean.TRUE); + attributes.put(Queue.MAXIMUM_DELIVERY_ATTEMPTS, 10); + AMQQueue queue = createTestQueue(getName(), getName() + "Owner", true, attributes); + DurableConfigurationStoreHelper.createQueue(_configStore, queue); + + // remove queue + DurableConfigurationStoreHelper.removeQueue(_configStore,queue); + reopenStore(); + verify(_recoveryHandler, never()).configuredObject(any(UUID.class), + eq(org.apache.qpid.server.model.Queue.class.getName()), + anyMap()); + } + + private AMQQueue createTestQueue(String queueName, + String queueOwner, + boolean exclusive, + final Map arguments) throws AMQStoreException + { + return createTestQueue(queueName, queueOwner, exclusive, null, arguments); + } + + private AMQQueue createTestQueue(String queueName, + String queueOwner, + boolean exclusive, + Exchange alternateExchange, + final Map arguments) throws AMQStoreException + { + AMQQueue queue = mock(AMQQueue.class); + when(queue.getName()).thenReturn(queueName); + when(queue.getOwner()).thenReturn(queueOwner); + when(queue.isExclusive()).thenReturn(exclusive); + when(queue.getId()).thenReturn(_queueId); + when(queue.getAlternateExchange()).thenReturn(alternateExchange); + if(arguments != null && !arguments.isEmpty()) + { + when(queue.getAvailableAttributes()).thenReturn(arguments.keySet()); + final ArgumentCaptor requestedAttribute = ArgumentCaptor.forClass(String.class); + when(queue.getAttribute(requestedAttribute.capture())).then( + new Answer() + { + + @Override + public Object answer(final InvocationOnMock invocation) throws Throwable + { + String attrName = requestedAttribute.getValue(); + return arguments.get(attrName); + } + }); + } + + return queue; + } + + private Exchange createTestExchange() + { + Exchange exchange = mock(Exchange.class); + when(exchange.getName()).thenReturn(getName()); + when(exchange.getTypeName()).thenReturn(getName() + "Type"); + when(exchange.isAutoDelete()).thenReturn(true); + when(exchange.getId()).thenReturn(_exchangeId); + return exchange; + } + + private void reopenStore() throws Exception + { + closeMessageStore(); + closeConfigStore(); + _messageStore = createMessageStore(); + _configStore = createConfigStore(); + + _configStore.configureConfigStore(_virtualHost, _recoveryHandler); + _messageStore.configureMessageStore(_virtualHost, _messageStoreRecoveryHandler, _logRecoveryHandler); + _messageStore.activate(); + } + + protected abstract MessageStore createMessageStore() throws Exception; + protected abstract DurableConfigurationStore createConfigStore() throws Exception; + protected abstract void closeMessageStore() throws Exception; + protected abstract void closeConfigStore() throws Exception; + + public void testRecordXid() throws Exception + { + Record enqueueRecord = getTestRecord(1); + Record dequeueRecord = getTestRecord(2); + Record[] enqueues = { enqueueRecord }; + Record[] dequeues = { dequeueRecord }; + byte[] globalId = new byte[] { 1 }; + byte[] branchId = new byte[] { 2 }; + + Transaction transaction = _messageStore.newTransaction(); + transaction.recordXid(1l, globalId, branchId, enqueues, dequeues); + transaction.commitTran(); + reopenStore(); + verify(_dtxRecordRecoveryHandler).dtxRecord(1l, globalId, branchId, enqueues, dequeues); + + transaction = _messageStore.newTransaction(); + transaction.removeXid(1l, globalId, branchId); + transaction.commitTran(); + + reopenStore(); + verify(_dtxRecordRecoveryHandler, times(1)).dtxRecord(1l, globalId, branchId, enqueues, dequeues); + } + + private Record getTestRecord(long messageNumber) + { + UUID queueId1 = UUIDGenerator.generateRandomUUID(); + TransactionLogResource queue1 = mock(TransactionLogResource.class); + when(queue1.getId()).thenReturn(queueId1); + EnqueableMessage message1 = mock(EnqueableMessage.class); + when(message1.isPersistent()).thenReturn(true); + when(message1.getMessageNumber()).thenReturn(messageNumber); + final StoredMessage storedMessage = mock(StoredMessage.class); + when(storedMessage.getMessageNumber()).thenReturn(messageNumber); + when(message1.getStoredMessage()).thenReturn(storedMessage); + Record enqueueRecord = new TestRecord(queue1, message1); + return enqueueRecord; + } + + private static class TestRecord implements Record + { + private TransactionLogResource _queue; + private EnqueableMessage _message; + + public TestRecord(TransactionLogResource queue, EnqueableMessage message) + { + super(); + _queue = queue; + _message = message; + } + + @Override + public TransactionLogResource getQueue() + { + return _queue; + } + + @Override + public EnqueableMessage getMessage() + { + return _message; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((_message == null) ? 0 : new Long(_message.getMessageNumber()).hashCode()); + result = prime * result + ((_queue == null) ? 0 : _queue.getId().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (!(obj instanceof Record)) + { + return false; + } + Record other = (Record) obj; + if (_message == null && other.getMessage() != null) + { + return false; + } + if (_queue == null && other.getQueue() != null) + { + return false; + } + if (_message.getMessageNumber() != other.getMessage().getMessageNumber()) + { + return false; + } + return _queue.getId().equals(other.getQueue().getId()); + } + + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/EventManagerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/EventManagerTest.java new file mode 100644 index 0000000000..2be79c5839 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/EventManagerTest.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.store; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.apache.qpid.server.store.Event.AFTER_ACTIVATE; +import static org.apache.qpid.server.store.Event.BEFORE_ACTIVATE; +import junit.framework.TestCase; + +public class EventManagerTest extends TestCase +{ + private EventManager _eventManager = new EventManager(); + private EventListener _mockListener = mock(EventListener.class); + + public void testEventListenerFires() + { + _eventManager.addEventListener(_mockListener, BEFORE_ACTIVATE); + _eventManager.notifyEvent(BEFORE_ACTIVATE); + verify(_mockListener).event(BEFORE_ACTIVATE); + } + + public void testEventListenerDoesntFire() + { + _eventManager.addEventListener(_mockListener, BEFORE_ACTIVATE); + _eventManager.notifyEvent(AFTER_ACTIVATE); + verifyZeroInteractions(_mockListener); + } + + public void testEventListenerFiresMulitpleTimes() + { + _eventManager.addEventListener(_mockListener, BEFORE_ACTIVATE); + _eventManager.addEventListener(_mockListener, AFTER_ACTIVATE); + + _eventManager.notifyEvent(BEFORE_ACTIVATE); + verify(_mockListener).event(BEFORE_ACTIVATE); + + _eventManager.notifyEvent(AFTER_ACTIVATE); + verify(_mockListener).event(AFTER_ACTIVATE); + } + + public void testMultipleListenersFireForSameEvent() + { + final EventListener mockListener1 = mock(EventListener.class); + final EventListener mockListener2 = mock(EventListener.class); + + _eventManager.addEventListener(mockListener1, BEFORE_ACTIVATE); + _eventManager.addEventListener(mockListener2, BEFORE_ACTIVATE); + _eventManager.notifyEvent(BEFORE_ACTIVATE); + + verify(mockListener1).event(BEFORE_ACTIVATE); + verify(mockListener2).event(BEFORE_ACTIVATE); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/JsonFileConfigStoreTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/JsonFileConfigStoreTest.java new file mode 100644 index 0000000000..b6300e6f48 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/JsonFileConfigStoreTest.java @@ -0,0 +1,299 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; +import org.mockito.InOrder; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class JsonFileConfigStoreTest extends QpidTestCase +{ + private final ConfigurationRecoveryHandler _recoveryHandler = mock(ConfigurationRecoveryHandler.class); + private VirtualHost _virtualHost; + private JsonFileConfigStore _store; + + @Override + public void setUp() throws Exception + { + super.setUp(); + removeStoreFile(); + _virtualHost = mock(VirtualHost.class); + when(_virtualHost.getName()).thenReturn(getName()); + when(_virtualHost.getAttribute(VirtualHost.CONFIG_STORE_PATH)).thenReturn(TMP_FOLDER); + _store = new JsonFileConfigStore(); + } + + @Override + public void tearDown() throws Exception + { + removeStoreFile(); + } + + private void removeStoreFile() + { + File file = new File(TMP_FOLDER, getName() + ".json"); + if(file.exists()) + { + file.delete(); + } + } + + public void testNoStorePath() throws Exception + { + when(_virtualHost.getAttribute(VirtualHost.CONFIG_STORE_PATH)).thenReturn(null); + try + { + _store.configureConfigStore(_virtualHost, _recoveryHandler); + fail("Store should not successfully configure if there is no path set"); + } + catch (AMQStoreException e) + { + // pass + } + } + + + public void testInvalidStorePath() throws Exception + { + when(_virtualHost.getAttribute(VirtualHost.CONFIG_STORE_PATH)).thenReturn(System.getProperty("file.separator")); + try + { + _store.configureConfigStore(_virtualHost, _recoveryHandler); + fail("Store should not successfully configure if there is an invalid path set"); + } + catch (AMQStoreException e) + { + // pass + } + } + + public void testStartFromNoStore() throws Exception + { + _store.configureConfigStore(_virtualHost, _recoveryHandler); + InOrder inorder = inOrder(_recoveryHandler); + inorder.verify(_recoveryHandler).beginConfigurationRecovery(eq(_store), eq(0)); + inorder.verify(_recoveryHandler,never()).configuredObject(any(UUID.class),anyString(),anyMap()); + inorder.verify(_recoveryHandler).completeConfigurationRecovery(); + _store.close(); + } + + public void testUpdatedConfigVersionIsRetained() throws Exception + { + final int NEW_CONFIG_VERSION = 42; + when(_recoveryHandler.completeConfigurationRecovery()).thenReturn(NEW_CONFIG_VERSION); + + _store.configureConfigStore(_virtualHost, _recoveryHandler); + _store.close(); + + _store.configureConfigStore(_virtualHost, _recoveryHandler); + InOrder inorder = inOrder(_recoveryHandler); + + // first time the config version should be the initial version - 0 + inorder.verify(_recoveryHandler).beginConfigurationRecovery(eq(_store), eq(0)); + + // second time the config version should be the updated version + inorder.verify(_recoveryHandler).beginConfigurationRecovery(eq(_store), eq(NEW_CONFIG_VERSION)); + + _store.close(); + } + + public void testCreateObject() throws Exception + { + _store.configureConfigStore(_virtualHost, _recoveryHandler); + final UUID queueId = new UUID(0, 1); + final String queueType = Queue.class.getSimpleName(); + final Map queueAttr = Collections.singletonMap("name", (Object) "q1"); + + _store.create(queueId, queueType, queueAttr); + _store.close(); + + _store.configureConfigStore(_virtualHost, _recoveryHandler); + verify(_recoveryHandler).configuredObject(eq(queueId), eq(queueType), eq(queueAttr)); + _store.close(); + } + + public void testCreateAndUpdateObject() throws Exception + { + _store.configureConfigStore(_virtualHost, _recoveryHandler); + final UUID queueId = new UUID(0, 1); + final String queueType = Queue.class.getSimpleName(); + Map queueAttr = Collections.singletonMap("name", (Object) "q1"); + + _store.create(queueId, queueType, queueAttr); + + + queueAttr = new HashMap(queueAttr); + queueAttr.put("owner", "theowner"); + _store.update(queueId, queueType, queueAttr); + + _store.close(); + + _store.configureConfigStore(_virtualHost, _recoveryHandler); + verify(_recoveryHandler).configuredObject(eq(queueId), eq(queueType), eq(queueAttr)); + _store.close(); + } + + + public void testCreateAndRemoveObject() throws Exception + { + _store.configureConfigStore(_virtualHost, _recoveryHandler); + final UUID queueId = new UUID(0, 1); + final String queueType = Queue.class.getSimpleName(); + Map queueAttr = Collections.singletonMap("name", (Object) "q1"); + + _store.create(queueId, queueType, queueAttr); + + + _store.remove(queueId, queueType); + + _store.close(); + + _store.configureConfigStore(_virtualHost, _recoveryHandler); + verify(_recoveryHandler, never()).configuredObject(any(UUID.class), anyString(), anyMap()); + _store.close(); + } + + public void testCreateUnknownObjectType() throws Exception + { + _store.configureConfigStore(_virtualHost, _recoveryHandler); + try + { + _store.create(UUID.randomUUID(), "wibble", Collections.emptyMap()); + fail("Should not be able to create instance of type wibble"); + } + catch (AMQStoreException e) + { + // pass + } + } + + public void testTwoObjectsWithSameId() throws Exception + { + _store.configureConfigStore(_virtualHost, _recoveryHandler); + final UUID id = UUID.randomUUID(); + _store.create(id, "Queue", Collections.emptyMap()); + try + { + _store.create(id, "Exchange", Collections.emptyMap()); + fail("Should not be able to create two objects with same id"); + } + catch (AMQStoreException e) + { + // pass + } + } + + + public void testChangeTypeOfObject() throws Exception + { + _store.configureConfigStore(_virtualHost, _recoveryHandler); + final UUID id = UUID.randomUUID(); + _store.create(id, "Queue", Collections.emptyMap()); + _store.close(); + _store.configureConfigStore(_virtualHost, _recoveryHandler); + + try + { + _store.update(id, "Exchange", Collections.emptyMap()); + fail("Should not be able to update object to different type"); + } + catch (AMQStoreException e) + { + // pass + } + } + + public void testLockFileGuaranteesExclusiveAccess() throws Exception + { + _store.configureConfigStore(_virtualHost, _recoveryHandler); + + JsonFileConfigStore secondStore = new JsonFileConfigStore(); + + try + { + secondStore.configureConfigStore(_virtualHost, _recoveryHandler); + fail("Should not be able to open a second store with the same path"); + } + catch(AMQStoreException e) + { + // pass + } + _store.close(); + secondStore.configureConfigStore(_virtualHost, _recoveryHandler); + + + } + + public void testCreatedNestedObjects() throws Exception + { + + _store.configureConfigStore(_virtualHost, _recoveryHandler); + final UUID queueId = new UUID(0, 1); + final UUID queue2Id = new UUID(1, 1); + + final Map EMPTY_ATTR = Collections.emptyMap(); + final UUID exchangeId = new UUID(0, 2); + final Map bindingAttributes = new HashMap(); + bindingAttributes.put(Binding.EXCHANGE, exchangeId); + bindingAttributes.put(Binding.QUEUE, queueId); + final Map binding2Attributes = new HashMap(); + binding2Attributes.put(Binding.EXCHANGE, exchangeId); + binding2Attributes.put(Binding.QUEUE, queue2Id); + + final UUID bindingId = new UUID(0, 3); + final UUID binding2Id = new UUID(1, 3); + + _store.create(queueId, "Queue", EMPTY_ATTR); + _store.create(queue2Id, "Queue", EMPTY_ATTR); + _store.create(exchangeId, "Exchange", EMPTY_ATTR); + _store.update(true, + new ConfiguredObjectRecord(bindingId, "Binding", bindingAttributes), + new ConfiguredObjectRecord(binding2Id, "Binding", binding2Attributes)); + _store.close(); + _store.configureConfigStore(_virtualHost, _recoveryHandler); + verify(_recoveryHandler).configuredObject(eq(queueId), eq("Queue"), eq(EMPTY_ATTR)); + verify(_recoveryHandler).configuredObject(eq(queue2Id), eq("Queue"), eq(EMPTY_ATTR)); + verify(_recoveryHandler).configuredObject(eq(exchangeId), eq("Exchange"), eq(EMPTY_ATTR)); + verify(_recoveryHandler).configuredObject(eq(bindingId),eq("Binding"), eq(bindingAttributes)); + verify(_recoveryHandler).configuredObject(eq(binding2Id),eq("Binding"), eq(binding2Attributes)); + _store.close(); + + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreQuotaEventsTestBase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreQuotaEventsTestBase.java new file mode 100644 index 0000000000..b23890b10c --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreQuotaEventsTestBase.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.store; + +import java.io.File; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.plugin.MessageMetaDataType; +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.test.utils.QpidTestCase; +import org.apache.qpid.util.FileUtils; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public abstract class MessageStoreQuotaEventsTestBase extends QpidTestCase implements EventListener, TransactionLogResource +{ + private static final Logger _logger = Logger.getLogger(MessageStoreQuotaEventsTestBase.class); + + protected static final byte[] MESSAGE_DATA = new byte[32 * 1024]; + + private MessageStore _store; + private File _storeLocation; + + private List _events; + private UUID _transactionResource; + + protected abstract MessageStore createStore() throws Exception; + + protected abstract void applyStoreSpecificConfiguration(VirtualHost virtualHost); + + protected abstract int getNumberOfMessagesToFillStore(); + + @Override + public void setUp() throws Exception + { + super.setUp(); + + _storeLocation = new File(new File(TMP_FOLDER), getTestName()); + FileUtils.delete(_storeLocation, true); + + + VirtualHost vhost = mock(VirtualHost.class); + when(vhost.getAttribute(eq(VirtualHost.STORE_PATH))).thenReturn(_storeLocation.getAbsolutePath()); + when(vhost.getName()).thenReturn("test"); + + applyStoreSpecificConfiguration(vhost); + + _store = createStore(); + ((DurableConfigurationStore)_store).configureConfigStore(vhost, null); + _store.configureMessageStore(vhost, null, null); + + _transactionResource = UUID.randomUUID(); + _events = new ArrayList(); + _store.addEventListener(this, Event.PERSISTENT_MESSAGE_SIZE_OVERFULL, Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL); + } + + @Override + public void tearDown() throws Exception + { + try + { + super.tearDown(); + } + finally + { + if (_store != null) + { + _store.close(); + } + FileUtils.delete(_storeLocation, true); + } + } + + public void testOverflow() throws Exception + { + Transaction transaction = _store.newTransaction(); + + List messages = new ArrayList(); + for (int i = 0; i < getNumberOfMessagesToFillStore(); i++) + { + EnqueableMessage m = addMessage(i); + messages.add(m); + transaction.enqueueMessage(this, m); + } + transaction.commitTran(); + + assertEvent(1, Event.PERSISTENT_MESSAGE_SIZE_OVERFULL); + + for (EnqueableMessage m : messages) + { + m.getStoredMessage().remove(); + } + + assertEvent(2, Event.PERSISTENT_MESSAGE_SIZE_UNDERFULL); + } + + protected EnqueableMessage addMessage(long id) + { + StorableMessageMetaData metaData = createMetaData(id, MESSAGE_DATA.length); + StoredMessage handle = _store.addMessage(metaData); + handle.addContent(0, ByteBuffer.wrap(MESSAGE_DATA)); + TestMessage message = new TestMessage(id, handle); + return message; + } + + private StorableMessageMetaData createMetaData(long id, int length) + { + StorableMessageMetaData metaData = mock(StorableMessageMetaData.class); + when(metaData.isPersistent()).thenReturn(true); + when(metaData.getContentSize()).thenReturn(length); + when(metaData.getStorableSize()).thenReturn(0); + MessageMetaDataType type = mock(MessageMetaDataType.class); + when(type.ordinal()).thenReturn(-1); + when(metaData.getType()).thenReturn(type); + return metaData; + } + + @Override + public void event(Event event) + { + _logger.debug("Test event listener received event " + event); + _events.add(event); + } + + private void assertEvent(int expectedNumberOfEvents, Event... expectedEvents) + { + assertEquals("Unexpected number of events received ", expectedNumberOfEvents, _events.size()); + for (Event event : expectedEvents) + { + assertTrue("Expected event is not found:" + event, _events.contains(event)); + } + } + + @Override + public UUID getId() + { + return _transactionResource; + } + + private static class TestMessage implements EnqueableMessage + { + private final StoredMessage _handle; + private final long _messageId; + + public TestMessage(long messageId, StoredMessage handle) + { + _messageId = messageId; + _handle = handle; + } + + public long getMessageNumber() + { + return _messageId; + } + + public boolean isPersistent() + { + return true; + } + + public StoredMessage getStoredMessage() + { + return _handle; + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreTestCase.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreTestCase.java new file mode 100644 index 0000000000..7ebfd54df6 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/MessageStoreTestCase.java @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.store; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.qpid.server.model.VirtualHost; +import org.apache.qpid.server.store.MessageStoreRecoveryHandler.StoredMessageRecoveryHandler; +import org.apache.qpid.test.utils.QpidTestCase; + +public abstract class MessageStoreTestCase extends QpidTestCase +{ + private ConfigurationRecoveryHandler _recoveryHandler; + private MessageStoreRecoveryHandler _messageStoreRecoveryHandler; + private StoredMessageRecoveryHandler _storedMessageRecoveryHandler; + private TransactionLogRecoveryHandler _logRecoveryHandler; + private TransactionLogRecoveryHandler.QueueEntryRecoveryHandler _queueEntryRecoveryHandler; + private TransactionLogRecoveryHandler.DtxRecordRecoveryHandler _dtxRecordRecoveryHandler; + private VirtualHost _virtualHost; + + private MessageStore _store; + + public void setUp() throws Exception + { + super.setUp(); + + _recoveryHandler = mock(ConfigurationRecoveryHandler.class); + _storedMessageRecoveryHandler = mock(StoredMessageRecoveryHandler.class); + _logRecoveryHandler = mock(TransactionLogRecoveryHandler.class); + _messageStoreRecoveryHandler = mock(MessageStoreRecoveryHandler.class); + _queueEntryRecoveryHandler = mock(TransactionLogRecoveryHandler.QueueEntryRecoveryHandler.class); + _dtxRecordRecoveryHandler = mock(TransactionLogRecoveryHandler.DtxRecordRecoveryHandler.class); + _virtualHost = mock(VirtualHost.class); + + + when(_messageStoreRecoveryHandler.begin()).thenReturn(_storedMessageRecoveryHandler); + when(_logRecoveryHandler.begin(any(MessageStore.class))).thenReturn(_queueEntryRecoveryHandler); + when(_queueEntryRecoveryHandler.completeQueueEntryRecovery()).thenReturn(_dtxRecordRecoveryHandler); + + setUpStoreConfiguration(_virtualHost); + when(_virtualHost.getName()).thenReturn(getTestName()); + + _store = createMessageStore(); + ((DurableConfigurationStore)_store).configureConfigStore(_virtualHost, _recoveryHandler); + + _store.configureMessageStore(_virtualHost, _messageStoreRecoveryHandler, _logRecoveryHandler); + } + + protected abstract void setUpStoreConfiguration(VirtualHost virtualHost) throws Exception; + + protected abstract MessageStore createMessageStore(); + + public MessageStore getStore() + { + return _store; + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/OperationalLoggingListenerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/OperationalLoggingListenerTest.java new file mode 100644 index 0000000000..c309dad5eb --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/OperationalLoggingListenerTest.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.store; + +import java.util.ArrayList; +import java.util.List; +import junit.framework.TestCase; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ConfigStoreMessages; +import org.apache.qpid.server.logging.messages.MessageStoreMessages; +import org.apache.qpid.server.logging.messages.TransactionLogMessages; + +public class OperationalLoggingListenerTest extends TestCase +{ + + + public static final String STORE_LOCATION = "The moon!"; + + protected void setUp() throws Exception + { + super.setUp(); + + } + + public void testOperationalLoggingWithStoreLocation() throws Exception + { + TestMessageStore messageStore = new TestMessageStore(); + LogSubject logSubject = LOG_SUBJECT; + + OperationalLoggingListener.listen(messageStore, logSubject); + + performTests(messageStore, true); + + } + + public void testOperationalLogging() throws Exception + { + TestMessageStore messageStore = new TestMessageStore(); + LogSubject logSubject = LOG_SUBJECT; + + OperationalLoggingListener.listen(messageStore, logSubject); + + performTests(messageStore, false); + } + + private void performTests(TestMessageStore messageStore, boolean setStoreLocation) + { + final List messages = new ArrayList(); + + CurrentActor.set(new TestActor(messages)); + + if(setStoreLocation) + { + messageStore.setStoreLocation(STORE_LOCATION); + } + + + messageStore.attainState(State.INITIALISING); + assertEquals("Unexpected number of operational log messages on configuring", 1, messages.size()); + assertEquals(messages.remove(0).toString(), ConfigStoreMessages.CREATED().toString()); + + messageStore.attainState(State.INITIALISED); + assertEquals("Unexpected number of operational log messages on CONFIGURED", setStoreLocation ? 3 : 2, messages.size()); + assertEquals(messages.remove(0).toString(), MessageStoreMessages.CREATED().toString()); + assertEquals(messages.remove(0).toString(), TransactionLogMessages.CREATED().toString()); + if(setStoreLocation) + { + assertEquals(messages.remove(0).toString(), MessageStoreMessages.STORE_LOCATION(STORE_LOCATION).toString()); + } + + messageStore.attainState(State.ACTIVATING); + assertEquals("Unexpected number of operational log messages on RECOVERING", 1, messages.size()); + assertEquals(messages.remove(0).toString(), MessageStoreMessages.RECOVERY_START().toString()); + + + messageStore.attainState(State.ACTIVE); + assertEquals("Unexpected number of operational log messages on ACTIVE", 1, messages.size()); + assertEquals(messages.remove(0).toString(), MessageStoreMessages.RECOVERY_COMPLETE().toString()); + + messageStore.attainState(State.CLOSING); + assertEquals("Unexpected number of operational log messages on CLOSING", 0, messages.size()); + + messageStore.attainState(State.CLOSED); + assertEquals("Unexpected number of operational log messages on CLOSED", 1, messages.size()); + assertEquals(messages.remove(0).toString(), MessageStoreMessages.CLOSED().toString()); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + CurrentActor.remove(); + } + + + private static final LogSubject LOG_SUBJECT = new LogSubject() + { + public String toLogString() + { + return ""; + } + }; + + private static final class TestMessageStore extends NullMessageStore + { + + private final EventManager _eventManager = new EventManager(); + private final StateManager _stateManager = new StateManager(_eventManager); + private String _storeLocation; + + public void attainState(State state) + { + _stateManager.attainState(state); + } + + @Override + public String getStoreLocation() + { + return _storeLocation; + } + + public void setStoreLocation(String storeLocation) + { + _storeLocation = storeLocation; + } + + @Override + public void addEventListener(EventListener eventListener, Event... events) + { + _eventManager.addEventListener(eventListener, events); + } + + @Override + public String getStoreType() + { + return "TEST"; + } + } + + private static class TestActor implements LogActor + { + private final List _messages; + + public TestActor(List messages) + { + _messages = messages; + } + + public void message(LogSubject subject, LogMessage message) + { + _messages.add(message); + } + + public void message(LogMessage message) + { + _messages.add(message); + } + + public RootMessageLogger getRootMessageLogger() + { + return null; + } + + public String getLogMessage() + { + return null; + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/StateManagerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/StateManagerTest.java new file mode 100644 index 0000000000..18efb976eb --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/StateManagerTest.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.store; + + +import java.util.EnumSet; +import junit.framework.TestCase; + +public class StateManagerTest extends TestCase implements EventListener +{ + + private StateManager _manager; + private Event _event; + + public void setUp() throws Exception + { + super.setUp(); + _manager = new StateManager(this); + } + + public void testInitialState() + { + assertEquals(State.INITIAL, _manager.getState()); + } + + public void testStateTransitionAllowed() + { + assertEquals(State.INITIAL, _manager.getState()); + + _manager.attainState(State.INITIALISING); + assertEquals(State.INITIALISING, _manager.getState()); + } + + public void testStateTransitionDisallowed() + { + assertEquals(State.INITIAL, _manager.getState()); + + try + { + _manager.attainState(State.CLOSING); + fail("Exception not thrown"); + } + catch (IllegalStateException e) + { + // PASS + } + assertEquals(State.INITIAL, _manager.getState()); + } + + public void testIsInState() + { + assertEquals(State.INITIAL, _manager.getState()); + assertFalse(_manager.isInState(State.ACTIVE)); + assertTrue(_manager.isInState(State.INITIAL)); + } + + public void testIsNotInState() + { + assertEquals(State.INITIAL, _manager.getState()); + assertTrue(_manager.isNotInState(State.ACTIVE)); + assertFalse(_manager.isNotInState(State.INITIAL)); + } + + public void testCheckInState() + { + assertEquals(State.INITIAL, _manager.getState()); + + try + { + _manager.checkInState(State.ACTIVE); + fail("Exception not thrown"); + } + catch (IllegalStateException e) + { + // PASS + } + assertEquals(State.INITIAL, _manager.getState()); + } + + public void testValidStateTransitions() + { + assertEquals(State.INITIAL, _manager.getState()); + performValidTransition(StateManager.INITIALISE); + performValidTransition(StateManager.INITALISE_COMPLETE); + performValidTransition(StateManager.ACTIVATE); + performValidTransition(StateManager.ACTIVATE_COMPLETE); + performValidTransition(StateManager.QUIESCE); + performValidTransition(StateManager.QUIESCE_COMPLETE); + performValidTransition(StateManager.RESTART); + performValidTransition(StateManager.ACTIVATE_COMPLETE); + performValidTransition(StateManager.CLOSE_ACTIVE); + performValidTransition(StateManager.CLOSE_COMPLETE); + + _manager = new StateManager(this); + assertEquals(State.INITIAL, _manager.getState()); + performValidTransition(StateManager.INITIALISE); + performValidTransition(StateManager.INITALISE_COMPLETE); + performValidTransition(StateManager.CLOSE_INITIALISED); + performValidTransition(StateManager.CLOSE_COMPLETE); + + _manager = new StateManager(this); + performValidTransition(StateManager.INITIALISE); + performValidTransition(StateManager.INITALISE_COMPLETE); + performValidTransition(StateManager.ACTIVATE); + performValidTransition(StateManager.ACTIVATE_COMPLETE); + performValidTransition(StateManager.QUIESCE); + performValidTransition(StateManager.QUIESCE_COMPLETE); + performValidTransition(StateManager.CLOSE_QUIESCED); + performValidTransition(StateManager.CLOSE_COMPLETE); + } + + private void performValidTransition(StateManager.Transition transition) + { + _manager.attainState(transition.getEndState()); + assertEquals("Unexpected end state", transition.getEndState(), _manager.getState()); + assertEquals("Unexpected event", transition.getEvent(), _event); + _event = null; + } + + public void testInvalidStateTransitions() + { + assertEquals(State.INITIAL, _manager.getState()); + + performInvalidTransitions(StateManager.INITIALISE, State.INITIALISED); + performInvalidTransitions(StateManager.INITALISE_COMPLETE, State.ACTIVATING, State.CLOSING); + performInvalidTransitions(StateManager.ACTIVATE, State.ACTIVE); + performInvalidTransitions(StateManager.ACTIVATE_COMPLETE, State.QUIESCING, State.CLOSING, State.INITIALISED); + performInvalidTransitions(StateManager.QUIESCE, State.QUIESCED); + performInvalidTransitions(StateManager.QUIESCE_COMPLETE, State.ACTIVATING, State.CLOSING); + performInvalidTransitions(StateManager.CLOSE_QUIESCED, State.CLOSED); + performInvalidTransitions(StateManager.CLOSE_COMPLETE); + + } + + private void performInvalidTransitions(StateManager.Transition preTransition, State... validEndStates) + { + if(preTransition != null) + { + performValidTransition(preTransition); + } + + EnumSet endStates = EnumSet.allOf(State.class); + + if(validEndStates != null) + { + for(State state: validEndStates) + { + endStates.remove(state); + } + } + + for(State invalidEndState : endStates) + { + performInvalidStateTransition(invalidEndState); + } + + + } + + private void performInvalidStateTransition(State invalidEndState) + { + try + { + _event = null; + State startState = _manager.getState(); + _manager.attainState(invalidEndState); + fail("Invalid state transition performed: " + startState + " to " + invalidEndState); + } + catch(IllegalStateException e) + { + // pass + } + assertNull("No event should have be fired", _event); + } + + @Override + public void event(Event event) + { + _event = event; + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.java new file mode 100644 index 0000000000..32df355c07 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStore.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.store; + + +/** A simple message store that stores the messages in a thread-safe structure in memory. */ +public class TestMemoryMessageStore extends AbstractMemoryMessageStore +{ + public static final String TYPE = "TestMemory"; + + @Override + public String getStoreType() + { + return TYPE; + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStoreFactory.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStoreFactory.java new file mode 100644 index 0000000000..fd2d4215ab --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestMemoryMessageStoreFactory.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.store; + +import java.util.Collections; +import java.util.Map; +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.plugin.MessageStoreFactory; + +public class TestMemoryMessageStoreFactory implements MessageStoreFactory +{ + + @Override + public String getType() + { + return TestMemoryMessageStore.TYPE; + } + + @Override + public MessageStore createMessageStore() + { + return new TestMemoryMessageStore(); + } + + @Override + public Map convertStoreConfiguration(Configuration configuration) + { + return Collections.emptyMap(); + } + + @Override + public void validateAttributes(Map attributes) + { + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.java new file mode 100644 index 0000000000..bb3c0cf535 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/store/TestableMemoryMessageStore.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.store; + +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.queue.AMQQueue; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Adds some extra methods to the memory message store for testing purposes. + */ +public class TestableMemoryMessageStore extends TestMemoryMessageStore +{ + private final Map _messages = new HashMap(); + private final AtomicInteger _messageCount = new AtomicInteger(0); + + @Override + public StoredMessage addMessage(StorableMessageMetaData metaData) + { + return new TestableStoredMessage(super.addMessage(metaData)); + } + + public int getMessageCount() + { + return _messageCount.get(); + } + + public Map getMessages() + { + return _messages; + } + + private class TestableTransaction implements Transaction + { + @Override + public void enqueueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException + { + getMessages().put(message.getMessageNumber(), (AMQQueue)queue); + } + + @Override + public void dequeueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException + { + getMessages().remove(message.getMessageNumber()); + } + + @Override + public void commitTran() throws AMQStoreException + { + } + + @Override + public StoreFuture commitTranAsync() throws AMQStoreException + { + return StoreFuture.IMMEDIATE_FUTURE; + } + + public void abortTran() throws AMQStoreException + { + } + + public void removeXid(long format, byte[] globalId, byte[] branchId) + { + } + + public void recordXid(long format, byte[] globalId, byte[] branchId, Record[] enqueues, Record[] dequeues) + { + } + } + + + @Override + public Transaction newTransaction() + { + return new TestableTransaction(); + } + + + private class TestableStoredMessage implements StoredMessage + { + private final StoredMessage _storedMessage; + + public TestableStoredMessage(StoredMessage storedMessage) + { + _messageCount.incrementAndGet(); + _storedMessage = storedMessage; + } + + public StorableMessageMetaData getMetaData() + { + return _storedMessage.getMetaData(); + } + + public long getMessageNumber() + { + return _storedMessage.getMessageNumber(); + } + + public void addContent(int offsetInMessage, ByteBuffer src) + { + _storedMessage.addContent(offsetInMessage, src); + } + + public int getContent(int offsetInMessage, ByteBuffer dst) + { + return _storedMessage.getContent(offsetInMessage, dst); + } + + + public ByteBuffer getContent(int offsetInMessage, int size) + { + return _storedMessage.getContent(offsetInMessage, size); + } + + public StoreFuture flushToStore() + { + return _storedMessage.flushToStore(); + } + + public void remove() + { + _storedMessage.remove(); + _messageCount.decrementAndGet(); + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/MockSubscription.java new file mode 100644 index 0000000000..b26d7530aa --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/MockSubscription.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.subscription; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.model.Port; +import org.apache.qpid.server.model.Transport; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.queue.QueueEntry.SubscriptionAcquiredState; +import org.apache.qpid.server.stats.StatisticsCounter; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class MockSubscription implements Subscription +{ + + private boolean _closed = false; + private String tag = "mocktag"; + private AMQQueue queue = null; + private StateListener _listener = null; + private volatile AMQQueue.Context _queueContext = null; + private State _state = State.ACTIVE; + private ArrayList messages = new ArrayList(); + private final Lock _stateChangeLock = new ReentrantLock(); + private List _acceptEntries = null; + + private final QueueEntry.SubscriptionAcquiredState _owningState = new QueueEntry.SubscriptionAcquiredState(this); + + private static final AtomicLong idGenerator = new AtomicLong(0); + // Create a simple ID that increments for ever new Subscription + private final long _subscriptionID = idGenerator.getAndIncrement(); + private boolean _isActive = true; + + public MockSubscription() + { + } + + public MockSubscription(List acceptEntries) + { + _acceptEntries = acceptEntries; + } + + public void close() + { + _closed = true; + if (_listener != null) + { + _listener.stateChange(this, _state, State.CLOSED); + } + _state = State.CLOSED; + } + + public String getConsumerName() + { + return tag; + } + + public long getSubscriptionID() + { + return _subscriptionID; + } + + public AMQQueue.Context getQueueContext() + { + return _queueContext; + } + + public SubscriptionAcquiredState getOwningState() + { + return _owningState; + } + + public LogActor getLogActor() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isTransient() + { + return false; + } + + public long getBytesOut() + { + return 0; // TODO - Implement + } + + public long getMessagesOut() + { + return 0; // TODO - Implement + } + + public long getUnacknowledgedBytes() + { + return 0; // TODO - Implement + } + + public long getUnacknowledgedMessages() + { + return 0; // TODO - Implement + } + + public AMQQueue getQueue() + { + return queue; + } + + public AMQSessionModel getSessionModel() + { + return new MockSessionModel(); + } + + public boolean trySendLock() + { + return _stateChangeLock.tryLock(); + } + + + public void getSendLock() + { + _stateChangeLock.lock(); + } + + public boolean hasInterest(QueueEntry entry) + { + if(_acceptEntries != null) + { + //simulate selector behaviour, only signal + //interest in the dictated queue entries + return _acceptEntries.contains(entry); + } + + return true; + } + + public boolean isActive() + { + return _isActive ; + } + + public void set(String key, Object value) + { + } + + public Object get(String key) + { + return null; + } + + public boolean isAutoClose() + { + return false; + } + + public boolean isClosed() + { + return _closed; + } + + public boolean acquires() + { + return true; + } + + public boolean seesRequeues() + { + return true; + } + + public boolean isSuspended() + { + return false; + } + + public void queueDeleted(AMQQueue queue) + { + } + + public void releaseSendLock() + { + _stateChangeLock.unlock(); + } + + public void onDequeue(QueueEntry queueEntry) + { + } + + public void restoreCredit(QueueEntry queueEntry) + { + } + + public void releaseQueueEntry(QueueEntry queueEntry) + { + } + + public void send(QueueEntry entry, boolean batch) throws AMQException + { + if (messages.contains(entry)) + { + entry.setRedelivered(); + } + messages.add(entry); + } + + public void flushBatched() + { + + } + + public void setQueueContext(AMQQueue.Context queueContext) + { + _queueContext = queueContext; + } + + public void setQueue(AMQQueue queue, boolean exclusive) + { + this.queue = queue; + } + + public void setNoLocal(boolean noLocal) + { + } + + public void setStateListener(StateListener listener) + { + this._listener = listener; + } + + public State getState() + { + return _state; + } + + public boolean wouldSuspend(QueueEntry msg) + { + return false; + } + + public ArrayList getMessages() + { + return messages; + } + + public boolean isSessionTransactional() + { + return false; + } + + public void queueEmpty() throws AMQException + { + } + + public void setActive(final boolean isActive) + { + _isActive = isActive; + } + + private static class MockSessionModel implements AMQSessionModel + { + private final UUID _id = UUID.randomUUID(); + + @Override + public UUID getId() + { + return _id; + } + + @Override + public AMQConnectionModel getConnectionModel() + { + return new MockConnectionModel(); + } + + @Override + public String getClientID() + { + return null; + } + + @Override + public void close() throws AMQException + { + } + + @Override + public LogSubject getLogSubject() + { + return null; + } + + @Override + public void checkTransactionStatus(long openWarn, long openClose, + long idleWarn, long idleClose) throws AMQException + { + } + + @Override + public void block(AMQQueue queue) + { + } + + @Override + public void unblock(AMQQueue queue) + { + } + + @Override + public void block() + { + } + + @Override + public void unblock() + { + } + + @Override + public boolean getBlocking() + { + return false; + } + + @Override + public boolean onSameConnection(InboundMessage inbound) + { + return false; + } + + @Override + public int getUnacknowledgedMessageCount() + { + return 0; + } + + @Override + public Long getTxnCount() + { + return null; + } + + @Override + public Long getTxnStart() + { + return null; + } + + @Override + public Long getTxnCommits() + { + return null; + } + + @Override + public Long getTxnRejects() + { + return null; + } + + @Override + public int getChannelId() + { + return 0; + } + + @Override + public int getConsumerCount() + { + return 0; + } + + @Override + public int compareTo(AMQSessionModel o) + { + return getId().compareTo(o.getId()); + } + + @Override + public void close(AMQConstant cause, String message) throws AMQException + { + } + } + + private static class MockConnectionModel implements AMQConnectionModel + { + @Override + public void initialiseStatistics() + { + } + + @Override + public void registerMessageReceived(long messageSize, long timestamp) + { + } + + @Override + public void registerMessageDelivered(long messageSize) + { + } + + @Override + public StatisticsCounter getMessageDeliveryStatistics() + { + return null; + } + + @Override + public StatisticsCounter getMessageReceiptStatistics() + { + return null; + } + + @Override + public StatisticsCounter getDataDeliveryStatistics() + { + return null; + } + + @Override + public StatisticsCounter getDataReceiptStatistics() + { + return null; + } + + @Override + public void resetStatistics() + { + + } + + @Override + public void close(AMQConstant cause, String message) + throws AMQException + { + } + + @Override + public void closeSession(AMQSessionModel session, AMQConstant cause, + String message) throws AMQException + { + } + + @Override + public long getConnectionId() + { + return 0; + } + + @Override + public List getSessionModels() + { + return null; + } + + @Override + public void block() + { + } + + @Override + public void unblock() + { + } + + @Override + public LogSubject getLogSubject() + { + return null; + } + + @Override + public String getUserName() + { + return null; + } + + @Override + public boolean isSessionNameUnique(byte[] name) + { + return false; + } + + @Override + public String getRemoteAddressString() + { + return "remoteAddress:1234"; + } + + @Override + public String getClientId() + { + return null; + } + + @Override + public String getClientVersion() + { + return null; + } + + @Override + public String getPrincipalAsString() + { + return null; + } + + @Override + public long getSessionCountLimit() + { + return 0; + } + + @Override + public long getLastIoTime() + { + return 0; + } + + @Override + public Port getPort() + { + return null; + } + + @Override + public Transport getTransport() + { + return null; + } + + @Override + public void stop() + { + } + + @Override + public boolean isStopped() + { + return false; + } + + @Override + public String getVirtualHostName() + { + return null; + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/SubscriptionListTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/SubscriptionListTest.java new file mode 100644 index 0000000000..c4d1a1e614 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/subscription/SubscriptionListTest.java @@ -0,0 +1,429 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.subscription; + +import org.apache.qpid.server.subscription.SubscriptionList.SubscriptionNode; +import org.apache.qpid.server.subscription.SubscriptionList.SubscriptionNodeIterator; +import org.apache.qpid.test.utils.QpidTestCase; + +public class SubscriptionListTest extends QpidTestCase +{ + private SubscriptionList _subList; + private MockSubscription _sub1; + private MockSubscription _sub2; + private MockSubscription _sub3; + private SubscriptionNode _node; + + protected void setUp() + { + _subList = new SubscriptionList(); + + _sub1 = new MockSubscription(); + _sub2 = new MockSubscription(); + _sub3 = new MockSubscription(); + + _subList.add(_sub1); + _subList.add(_sub2); + _subList.add(_sub3); + + _node = _subList.getHead(); + } + + /** + * Test that if the first (non-head) node in the list is deleted (but is still present), + * it is not returned when searching through the list for the next viable node, and the + * subsequent viable node is returned instead. + */ + public void testFindNextSkipsFirstDeletedNode() + { + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub1).delete()); + + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 2nd subscription", _sub2, _node.getSubscription()); + + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 3rd subscription", _sub3, _node.getSubscription()); + } + + /** + * Test that if a central node in the list is deleted (but is still present), + * it is not returned when searching through the list for the next viable node, + * and the subsequent viable node is returned instead. + */ + public void testFindNextSkipsCentralDeletedNode() + { + assertNotNull("Returned node should not be null", _node = _node.findNext()); + + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub2).delete()); + + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 3rd subscription", _sub3, _node.getSubscription()); + } + + /** + * Test that if the last node in the list is deleted (but is still present), + * it is not returned when searching through the list for the next viable node, + * and null is returned instead. + */ + public void testFindNextSkipsLastDeletedNode() + { + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 1st subscription", _sub1, _node.getSubscription()); + + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 2nd subscription", _sub2, _node.getSubscription()); + + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub3).delete()); + + assertNull("Returned node should be null", _node = _node.findNext()); + } + + /** + * Test that if multiple nodes in the list are deleted (but still present), they + * are not returned when searching through the list for the next viable node, + * and the subsequent viable node is returned instead. + */ + public void testFindNextSkipsMultipleDeletedNode() + { + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub1).delete()); + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub2).delete()); + + assertNotNull("Returned node should not be null", _node = _node.findNext()); + assertEquals("Should have returned node for 3rd subscription", _sub3, _node.getSubscription()); + } + + /** + * Test that if a node in the list is marked 'deleted' it is still present in the list + * until actually removed. counter-test to verify above testing of getNext() method. + */ + public void testDeletedNodeStillPresent() + { + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub1).delete()); + + assertNotNull("Node marked deleted should still be present", getNodeForSubscription(_subList, _sub1)); + assertEquals("All 3 nodes are still expected to be present", 3, countNodes(_subList)); + } + + /** + * Traverses the list nodes in a non-mutating fashion, returning the first node which matches the given + * Subscription, or null if none is found. + */ + private SubscriptionNode getNodeForSubscription(final SubscriptionList list, final Subscription sub) + { + SubscriptionNode node = list.getHead(); + while (node != null && node.getSubscription() != sub) + { + node = node.nextNode(); + } + + return node; + } + + /** + * Counts the number of (non-head) nodes in the list. + */ + private int countNodes(final SubscriptionList list) + { + SubscriptionNode node = list.getHead(); + int count; + for(count = -1; node != null; count++) + { + node = node.nextNode(); + } + + return count; + } + + /** + * Tests that the head is returned as expected, and isn't the node for the first subscription. + */ + public void testGetHead() + { + assertNotNull("List head should be non null", _node); + assertNotSame("Head should not be node for first subscription", + _node, getNodeForSubscription(_subList, _sub1)); + } + + /** + * Tests that the size is returned correctly in the face of additions and removals. + */ + public void testGetSize() + { + SubscriptionList subList = new SubscriptionList(); + + assertEquals("Unexpected size result", 0, subList.size()); + + Subscription sub1 = new MockSubscription(); + Subscription sub2 = new MockSubscription(); + Subscription sub3 = new MockSubscription(); + + subList.add(sub1); + assertEquals("Unexpected size result", 1, subList.size()); + + subList.add(sub2); + assertEquals("Unexpected size result", 2, subList.size()); + + subList.add(sub3); + assertEquals("Unexpected size result", 3, subList.size()); + + assertTrue("Removing subscription from list should have succeeded", subList.remove(sub1)); + assertEquals("Unexpected size result", 2, subList.size()); + + assertTrue("Removing subscription from list should have succeeded", subList.remove(sub2)); + assertEquals("Unexpected size result", 1, subList.size()); + + assertTrue("Removing subscription from list should have succeeded", subList.remove(sub3)); + assertEquals("Unexpected size result", 0, subList.size()); + } + + /** + * Test that if the first (non-head) node in the list is removed it is no longer + * present in the node structure of the list at all. + */ + public void testRemoveFirstNode() + { + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub1)); + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub1)); + assertNull("Should not have been a node present for the removed subscription", getNodeForSubscription(_subList, _sub1)); + assertEquals("Unexpected number of nodes", 2, countNodes(_subList)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub2)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub3)); + } + + /** + * Test that if a central node in the list is removed it is no longer + * present in the node structure of the list at all. + */ + public void testRemoveCentralNode() + { + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub2)); + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub2)); + assertNull("Should not have been a node present for the removed subscription", getNodeForSubscription(_subList, _sub2)); + assertEquals("Unexpected number of nodes", 2, countNodes(_subList)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub1)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub3)); + } + + /** + * Test that if the subscription contained in the last node of the list is removed + * it is no longer present in the node structure of the list at all. However, + * as the last node in the structure can't actually be removed a dummy will instead + * be present. + */ + public void testRemoveLastNode() + { + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub3)); + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub3)); + assertNull("Should not have been a node present for the removed subscription", getNodeForSubscription(_subList, _sub3)); + + //We actually expect 3 nodes to remain this time, because the last node cant be removed for thread safety reasons, + //however a dummy final node can be used as substitute to allow removal of the subscription node. + assertEquals("Unexpected number of nodes", 2 + 1, countNodes(_subList)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub1)); + assertNotNull("Should have been a node present for the subscription", getNodeForSubscription(_subList, _sub2)); + } + + /** + * Test that if the subscription not contained in the list is requested to be removed + * that the removal fails + */ + public void testRemoveNonExistantNode() + { + Subscription sub4 = new MockSubscription(); + assertNull("Should not have been a node present for the subscription", getNodeForSubscription(_subList, sub4)); + assertFalse("Removing subscription node should not have succeeded", _subList.remove(sub4)); + assertEquals("Unexpected number of nodes", 3, countNodes(_subList)); + } + + /** + * Test that if a subscription node which occurs later in the main list than the marked node is + * removed from the list after the marked node is also removed, then the marker node doesn't + * serve to retain the subsequent nodes in the list structure (and thus memory) despite their + * removal. + */ + public void testDeletedMarkedNodeDoesntLeakSubsequentlyDeletedNodes() + { + //get the nodes out the list for the 1st and 3rd subscriptions + SubscriptionNode sub1Node = getNodeForSubscription(_subList, _sub1); + assertNotNull("Should have been a node present for the subscription", sub1Node); + SubscriptionNode sub3Node = getNodeForSubscription(_subList, _sub3); + assertNotNull("Should have been a node present for the subscription", sub3Node); + + //mark the first subscription node + assertTrue("should have succeeded in updating the marked node", + _subList.updateMarkedNode(_subList.getMarkedNode(), sub1Node)); + + //remove the 1st subscription from the list + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub1)); + //verify the 1st subscription is no longer the marker node (replaced by a dummy), or in the main list structure + assertNotSame("Unexpected marker node", sub1Node, _subList.getMarkedNode()); + assertNull("Should not have been a node present in the list structure for the marked-but-removed sub1 node", + getNodeForSubscription(_subList, _sub1)); + + //remove the 2nd subscription from the list + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub2)); + + //verify the marker node isn't leaking subsequently removed nodes, by ensuring the very next node + //in its list structure is now the 3rd subscription (since the 2nd was removed too) + assertEquals("Unexpected next node", sub3Node, _subList.getMarkedNode().nextNode()); + + //remove the 3rd and final/tail subscription + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub3)); + + //verify the marker node isn't leaking subsequently removed nodes, by ensuring the very next node + //in its list structure is now the dummy tail (since the 3rd subscription was removed, and a dummy + //tail was inserted) and NOT the 3rd sub node. + assertNotSame("Unexpected next node", sub3Node, _subList.getMarkedNode().nextNode()); + assertTrue("Unexpected next node", _subList.getMarkedNode().nextNode().isDeleted()); + assertNull("Next non-deleted node from the marker should now be the list end, i.e. null", _subList.getMarkedNode().findNext()); + } + + /** + * Test that the marked node 'findNext' behaviour is as expected after a subscription is added + * to the list following the tail subscription node being removed while it is the marked node. + * That is, that the new subscriptions node is returned by getMarkedNode().findNext(). + */ + public void testMarkedNodeFindsNewSubscriptionAfterRemovingTailWhilstMarked() + { + //get the node out the list for the 3rd subscription + SubscriptionNode sub3Node = getNodeForSubscription(_subList, _sub3); + assertNotNull("Should have been a node present for the subscription", sub3Node); + + //mark the 3rd subscription node + assertTrue("should have succeeded in updating the marked node", + _subList.updateMarkedNode(_subList.getMarkedNode(), sub3Node)); + + //verify calling findNext on the marked node returns null, i.e. the end of the list has been reached + assertEquals("Unexpected node after marked node", null, _subList.getMarkedNode().findNext()); + + //remove the 3rd(marked) subscription from the list + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub3)); + + //add a new 4th subscription to the list + Subscription sub4 = new MockSubscription(); + _subList.add(sub4); + + //get the node out the list for the 4th subscription + SubscriptionNode sub4Node = getNodeForSubscription(_subList, sub4); + assertNotNull("Should have been a node present for the subscription", sub4Node); + + //verify the marked node (which is now a dummy substitute for the 3rd subscription) returns + //the 4th subscriptions node as the next non-deleted node. + assertEquals("Unexpected next node", sub4Node, _subList.getMarkedNode().findNext()); + } + + /** + * Test that setting the marked node to null doesn't cause problems during remove operations + */ + public void testRemoveWithNullMarkedNode() + { + //set the marker to null + assertTrue("should have succeeded in updating the marked node", + _subList.updateMarkedNode(_subList.getMarkedNode(), null)); + + //remove the 1st subscription from the main list + assertTrue("Removing subscription node should have succeeded", _subList.remove(_sub1)); + + //verify the 1st subscription is no longer in the main list structure + assertNull("Should not have been a node present in the main list structure for sub1", + getNodeForSubscription(_subList, _sub1)); + assertEquals("Unexpected number of nodes", 2, countNodes(_subList)); + } + + /** + * Tests that after the first (non-head) node of the list is marked deleted but has not + * yet been removed, the iterator still skips it. + */ + public void testIteratorSkipsFirstDeletedNode() + { + //'delete' but dont remove the node for the 1st subscription + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub1).delete()); + assertNotNull("Should still have been a node present for the deleted subscription", + getNodeForSubscription(_subList, _sub1)); + + SubscriptionNodeIterator iter = _subList.iterator(); + + //verify the iterator returns the 2nd subscriptions node + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub2, iter.getNode().getSubscription()); + + //verify the iterator returns the 3rd subscriptions node and not the 2nd. + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub3, iter.getNode().getSubscription()); + } + + /** + * Tests that after a central node of the list is marked deleted but has not yet been removed, + * the iterator still skips it. + */ + public void testIteratorSkipsCentralDeletedNode() + { + //'delete' but dont remove the node for the 2nd subscription + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub2).delete()); + assertNotNull("Should still have been a node present for the deleted subscription", + getNodeForSubscription(_subList, _sub2)); + + SubscriptionNodeIterator iter = _subList.iterator(); + + //verify the iterator returns the 1st subscriptions node + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub1, iter.getNode().getSubscription()); + + //verify the iterator returns the 3rd subscriptions node and not the 2nd. + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub3, iter.getNode().getSubscription()); + } + + /** + * Tests that after the last node of the list is marked deleted but has not yet been removed, + * the iterator still skips it. + */ + public void testIteratorSkipsDeletedFinalNode() + { + //'delete' but dont remove the node for the 3rd subscription + assertTrue("Deleting subscription node should have succeeded", + getNodeForSubscription(_subList, _sub3).delete()); + assertNotNull("Should still have been a node present for the deleted 3rd subscription", + getNodeForSubscription(_subList, _sub3)); + + SubscriptionNodeIterator iter = _subList.iterator(); + + //verify the iterator returns the 1st subscriptions node + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub1, iter.getNode().getSubscription()); + + //verify the iterator returns the 2nd subscriptions node + assertTrue("Iterator should have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", _sub2, iter.getNode().getSubscription()); + + //verify the iterator can no longer advance and does not return a subscription node + assertFalse("Iterator should not have been able to advance", iter.advance()); + assertEquals("Iterator returned unexpected SubscriptionNode", null, iter.getNode()); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/AsyncAutoCommitTransactionTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/AsyncAutoCommitTransactionTest.java new file mode 100644 index 0000000000..5c1012d50b --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/AsyncAutoCommitTransactionTest.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 static org.mockito.Mockito.mock; +import static org.mockito.Mockito.*; + +import java.util.Collections; + +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.store.Transaction; +import org.apache.qpid.server.txn.AsyncAutoCommitTransaction.FutureRecorder; +import org.apache.qpid.server.txn.ServerTransaction.Action; +import org.apache.qpid.test.utils.QpidTestCase; + +public class AsyncAutoCommitTransactionTest extends QpidTestCase +{ + private static final String STRICT_ORDER_SYSTEM_PROPERTY = AsyncAutoCommitTransaction.QPID_STRICT_ORDER_WITH_MIXED_DELIVERY_MODE; + + private FutureRecorder _futureRecorder = mock(FutureRecorder.class); + private EnqueableMessage _message = mock(EnqueableMessage.class); + private BaseQueue _queue = mock(BaseQueue.class); + private MessageStore _messageStore = mock(MessageStore.class); + private Transaction _storeTransaction = mock(Transaction.class); + private Action _postTransactionAction = mock(Action.class); + private StoreFuture _future = mock(StoreFuture.class); + + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + when(_messageStore.newTransaction()).thenReturn(_storeTransaction); + when(_storeTransaction.commitTranAsync()).thenReturn(_future); + when(_queue.isDurable()).thenReturn(true); + } + + public void testEnqueuePersistentMessagePostCommitNotCalledWhenFutureAlreadyComplete() throws Exception + { + setTestSystemProperty(STRICT_ORDER_SYSTEM_PROPERTY, "false"); + + when(_message.isPersistent()).thenReturn(true); + when(_future.isComplete()).thenReturn(true); + + AsyncAutoCommitTransaction asyncAutoCommitTransaction = + new AsyncAutoCommitTransaction(_messageStore, _futureRecorder); + + asyncAutoCommitTransaction.enqueue(_queue, _message, _postTransactionAction); + + verify(_storeTransaction).enqueueMessage(_queue, _message); + verify(_futureRecorder).recordFuture(_future, _postTransactionAction); + verifyZeroInteractions(_postTransactionAction); + } + + public void testEnqueuePersistentMessageOnMultiplQueuesPostCommitNotCalled() throws Exception + { + setTestSystemProperty(STRICT_ORDER_SYSTEM_PROPERTY, "false"); + + when(_message.isPersistent()).thenReturn(true); + when(_future.isComplete()).thenReturn(true); + + AsyncAutoCommitTransaction asyncAutoCommitTransaction = + new AsyncAutoCommitTransaction(_messageStore, _futureRecorder); + + asyncAutoCommitTransaction.enqueue(Collections.singletonList(_queue), _message, _postTransactionAction); + + verify(_storeTransaction).enqueueMessage(_queue, _message); + verify(_futureRecorder).recordFuture(_future, _postTransactionAction); + verifyZeroInteractions(_postTransactionAction); + } + + public void testEnqueuePersistentMessagePostCommitNotCalledWhenFutureNotYetComplete() throws Exception + { + setTestSystemProperty(STRICT_ORDER_SYSTEM_PROPERTY, "false"); + + when(_message.isPersistent()).thenReturn(true); + when(_future.isComplete()).thenReturn(false); + + AsyncAutoCommitTransaction asyncAutoCommitTransaction = + new AsyncAutoCommitTransaction(_messageStore, _futureRecorder); + + asyncAutoCommitTransaction.enqueue(_queue, _message, _postTransactionAction); + + verify(_storeTransaction).enqueueMessage(_queue, _message); + verify(_futureRecorder).recordFuture(_future, _postTransactionAction); + verifyZeroInteractions(_postTransactionAction); + } + + public void testEnqueueTransientMessagePostCommitIsCalledWhenNotBehavingStrictly() throws Exception + { + setTestSystemProperty(STRICT_ORDER_SYSTEM_PROPERTY, "false"); + + when(_message.isPersistent()).thenReturn(false); + + AsyncAutoCommitTransaction asyncAutoCommitTransaction = + new AsyncAutoCommitTransaction(_messageStore, _futureRecorder); + + asyncAutoCommitTransaction.enqueue(_queue, _message, _postTransactionAction); + + verifyZeroInteractions(_storeTransaction); + verify(_postTransactionAction).postCommit(); + verifyZeroInteractions(_futureRecorder); + } + + public void testEnqueueTransientMessagePostCommitIsCalledWhenBehavingStrictly() throws Exception + { + setTestSystemProperty(STRICT_ORDER_SYSTEM_PROPERTY, "true"); + + when(_message.isPersistent()).thenReturn(false); + + AsyncAutoCommitTransaction asyncAutoCommitTransaction = + new AsyncAutoCommitTransaction(_messageStore, _futureRecorder); + + asyncAutoCommitTransaction.enqueue(_queue, _message, _postTransactionAction); + + verifyZeroInteractions(_storeTransaction); + verify(_futureRecorder).recordFuture(StoreFuture.IMMEDIATE_FUTURE, _postTransactionAction); + verifyZeroInteractions(_postTransactionAction); + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/AutoCommitTransactionTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/AutoCommitTransactionTest.java new file mode 100644 index 0000000000..06b8539eb1 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/AutoCommitTransactionTest.java @@ -0,0 +1,442 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.server.message.ServerMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.queue.MockQueueEntry; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.txn.MockStoreTransaction.TransactionState; +import org.apache.qpid.test.utils.QpidTestCase; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * A unit test ensuring that AutoCommitTransaction creates a separate transaction for + * each dequeue/enqueue operation that involves enlistable messages. Verifies + * that the transaction is properly committed (or rolled-back in the case of exception), + * and that post transaction actions are correctly fired. + * + */ +public class AutoCommitTransactionTest extends QpidTestCase +{ + private ServerTransaction _transaction = null; // Class under test + + private MessageStore _transactionLog; + private AMQQueue _queue; + private List _queues; + private Collection _queueEntries; + private ServerMessage _message; + private MockAction _action; + private MockStoreTransaction _storeTransaction; + + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + _storeTransaction = createTestStoreTransaction(false); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _action = new MockAction(); + + _transaction = new AutoCommitTransaction(_transactionLog); + } + + /** + * Tests the enqueue of a non persistent message to a single non durable queue. + * Asserts that a store transaction has not been started and commit action fired. + */ + public void testEnqueueToNonDurableQueueOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queue = createTestAMQQueue(false); + + _transaction.enqueue(_queue, _message, _action); + + assertEquals("Enqueue of non-persistent message must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + + } + + /** + * Tests the enqueue of a persistent message to a durable queue. + * Asserts that a store transaction has been committed and commit action fired. + */ + public void testEnqueueToDurableQueueOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.enqueue(_queue, _message, _action); + + assertEquals("Enqueue of persistent message to durable queue must cause message to be enqueued", 1, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted and rollback action is fired. + */ + public void testStoreEnqueueCausesException() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new AutoCommitTransaction(_transactionLog); + + try + { + _transaction.enqueue(_queue, _message, _action); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertTrue("Rollback action must be fired", _action.isRollbackActionFired()); + assertFalse("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the enqueue of a non persistent message to a many non durable queues. + * Asserts that a store transaction has not been started and post commit action fired. + */ + public void testEnqueueToManyNonDurableQueuesOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queues = createTestBaseQueues(new boolean[] {false, false, false}); + + _transaction.enqueue(_queues, _message, _action); + + assertEquals("Enqueue of non-persistent message must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + + } + + + /** + * Tests the enqueue of a persistent message to a many non durable queues. + * Asserts that a store transaction has not been started and post commit action + * fired. + */ + public void testEnqueueToManyNonDurableQueuesOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {false, false, false}); + + _transaction.enqueue(_queues, _message, _action); + + assertEquals("Enqueue of persistent message to non-durable queues must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + + } + + /** + * Tests the enqueue of a persistent message to many queues, some durable others not. + * Asserts that a store transaction has been committed and post commit action fired. + */ + public void testEnqueueToDurableAndNonDurableQueuesOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {false, true, false, true}); + + _transaction.enqueue(_queues, _message, _action); + + assertEquals("Enqueue of persistent message to durable/non-durable queues must cause messages to be enqueued", 2, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted and rollback action fired. + */ + public void testStoreEnqueuesCausesExceptions() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {true, true}); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new AutoCommitTransaction(_transactionLog); + + try + { + _transaction.enqueue(_queues, _message, _action); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertTrue("Rollback action must be fired", _action.isRollbackActionFired()); + assertFalse("Post commit action must not be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the dequeue of a non persistent message from a single non durable queue. + * Asserts that a store transaction has not been started and post commit action + * fired. + */ + public void testDequeueFromNonDurableQueueOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queue = createTestAMQQueue(false); + + _transaction.dequeue(_queue, _message, _action); + + assertEquals("Dequeue of non-persistent message must not cause message to be dequeued", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + + } + + /** + * Tests the dequeue of a persistent message from a single non durable queue. + * Asserts that a store transaction has not been started and post commit + * action fired. + */ + public void testDequeueFromDurableQueueOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.dequeue(_queue, _message, _action); + + assertEquals("Dequeue of persistent message to durable queue must cause message to be dequeued",1, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted and post rollback action + * fired. + */ + public void testStoreDequeueCausesException() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new AutoCommitTransaction(_transactionLog); + + try + { + _transaction.dequeue(_queue, _message, _action); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + + assertTrue("Rollback action must be fired", _action.isRollbackActionFired()); + assertFalse("Post commit action must not be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the dequeue of a non persistent message from many non durable queues. + * Asserts that a store transaction has not been started and post commit action + * fired. + */ + public void testDequeueFromManyNonDurableQueuesOfNonPersistentMessage() throws Exception + { + _queueEntries = createTestQueueEntries(new boolean[] {false, false, false}, new boolean[] {false, false, false}); + + _transaction.dequeue(_queueEntries, _action); + + assertEquals("Dequeue of non-persistent messages must not cause message to be dequeued", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertEquals("Rollback action must not be fired", false, _action.isRollbackActionFired()); + assertEquals("Post commit action must be fired", true, _action.isPostCommitActionFired()); + + } + + + /** + * Tests the dequeue of a persistent message from a many non durable queues. + * Asserts that a store transaction has not been started and post commit action + * fired. + */ + public void testDequeueFromManyNonDurableQueuesOfPersistentMessage() throws Exception + { + _queueEntries = createTestQueueEntries(new boolean[] {false, false, false}, new boolean[] {true, true, true}); + + _transaction.dequeue(_queueEntries, _action); + + assertEquals("Dequeue of persistent message from non-durable queues must not cause message to be enqueued", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the dequeue of a persistent message from many queues, some durable others not. + * Asserts that a store transaction has not been started and post commit action fired. + */ + public void testDequeueFromDurableAndNonDurableQueuesOfPersistentMessage() throws Exception + { + // A transaction will exist owing to the 1st and 3rd. + _queueEntries = createTestQueueEntries(new boolean[] {true, false, true, true}, new boolean[] {true, true, true, false}); + + _transaction.dequeue(_queueEntries, _action); + + assertEquals("Dequeue of persistent messages from durable/non-durable queues must cause messages to be dequeued", 2, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired", _action.isRollbackActionFired()); + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted and post rollback action fired. + */ + public void testStoreDequeuesCauseExceptions() throws Exception + { + // Transactions will exist owing to the 1st and 3rd queue entries in the collection + _queueEntries = createTestQueueEntries(new boolean[] {true}, new boolean[] {true}); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new AutoCommitTransaction(_transactionLog); + + try + { + _transaction.dequeue(_queueEntries, _action); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + + assertTrue("Rollback action must be fired", _action.isRollbackActionFired()); + assertFalse("Post commit action must not be fired", _action.isPostCommitActionFired()); + } + + /** + * Tests the add of a post-commit action. Since AutoCommitTranctions + * have no long lived transactions, the post commit action is fired immediately. + */ + public void testPostCommitActionFiredImmediately() throws Exception + { + + _transaction.addPostTransactionAction(_action); + + assertTrue("Post commit action must be fired", _action.isPostCommitActionFired()); + assertFalse("Rollback action must be fired", _action.isRollbackActionFired()); + } + + private Collection createTestQueueEntries(boolean[] queueDurableFlags, boolean[] messagePersistentFlags) + { + Collection queueEntries = new ArrayList(); + + assertTrue("Boolean arrays must be the same length", queueDurableFlags.length == messagePersistentFlags.length); + + for(int i = 0; i < queueDurableFlags.length; i++) + { + final AMQQueue queue = createTestAMQQueue(queueDurableFlags[i]); + final ServerMessage message = createTestMessage(messagePersistentFlags[i]); + + queueEntries.add(new MockQueueEntry() + { + + @Override + public ServerMessage getMessage() + { + return message; + } + + @Override + public AMQQueue getQueue() + { + return queue; + } + + }); + } + + return queueEntries; + } + + private MockStoreTransaction createTestStoreTransaction(boolean throwException) + { + return new MockStoreTransaction(throwException); + } + + private List createTestBaseQueues(boolean[] durableFlags) + { + List queues = new ArrayList(); + for (boolean b: durableFlags) + { + queues.add(createTestAMQQueue(b)); + } + + return queues; + } + + private AMQQueue createTestAMQQueue(final boolean durable) + { + return new MockAMQQueue("mockQueue") + { + @Override + public boolean isDurable() + { + return durable; + } + + }; + } + + private ServerMessage createTestMessage(final boolean persistent) + { + return new MockServerMessage(persistent); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/LocalTransactionTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/LocalTransactionTest.java new file mode 100644 index 0000000000..4904cbc6fb --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/LocalTransactionTest.java @@ -0,0 +1,672 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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.server.message.ServerMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.MockAMQQueue; +import org.apache.qpid.server.queue.MockQueueEntry; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.txn.MockStoreTransaction.TransactionState; +import org.apache.qpid.test.utils.QpidTestCase; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * A unit test ensuring that LocalTransactionTest creates a long-lived store transaction + * that spans many dequeue/enqueue operations of enlistable messages. Verifies + * that the long-lived transaction is properly committed and rolled back, and that + * post transaction actions are correctly fired. + * + */ +public class LocalTransactionTest extends QpidTestCase +{ + private ServerTransaction _transaction = null; // Class under test + + private AMQQueue _queue; + private List _queues; + private Collection _queueEntries; + private ServerMessage _message; + private MockAction _action1; + private MockAction _action2; + private MockStoreTransaction _storeTransaction; + private MessageStore _transactionLog; + + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + _storeTransaction = createTestStoreTransaction(false); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _action1 = new MockAction(); + _action2 = new MockAction(); + + _transaction = new LocalTransaction(_transactionLog); + + } + + + /** + * Tests the enqueue of a non persistent message to a single non durable queue. + * Asserts that a store transaction has not been started. + */ + public void testEnqueueToNonDurableQueueOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queue = createTestAMQQueue(false); + + _transaction.enqueue(_queue, _message, _action1); + + assertEquals("Enqueue of non-persistent message must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the enqueue of a persistent message to a durable queue. + * Asserts that a store transaction has been started. + */ + public void testEnqueueToDurableQueueOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.enqueue(_queue, _message, _action1); + + assertEquals("Enqueue of persistent message to durable queue must cause message to be enqueued", 1, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted. + */ + public void testStoreEnqueueCausesException() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new LocalTransaction(_transactionLog); + + try + { + _transaction.enqueue(_queue, _message, _action1); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertTrue("Rollback action must be fired", _action1.isRollbackActionFired()); + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + + assertFalse("Post commit action must not be fired", _action1.isPostCommitActionFired()); + + } + + /** + * Tests the enqueue of a non persistent message to a many non durable queues. + * Asserts that a store transaction has not been started. + */ + public void testEnqueueToManyNonDurableQueuesOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queues = createTestBaseQueues(new boolean[] {false, false, false}); + + _transaction.enqueue(_queues, _message, _action1); + + assertEquals("Enqueue of non-persistent message must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the enqueue of a persistent message to a many non durable queues. + * Asserts that a store transaction has not been started. + */ + public void testEnqueueToManyNonDurableQueuesOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {false, false, false}); + + _transaction.enqueue(_queues, _message, _action1); + + assertEquals("Enqueue of persistent message to non-durable queues must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + + } + + /** + * Tests the enqueue of a persistent message to many queues, some durable others not. + * Asserts that a store transaction has been started. + */ + public void testEnqueueToDurableAndNonDurableQueuesOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {false, true, false, true}); + + _transaction.enqueue(_queues, _message, _action1); + + assertEquals("Enqueue of persistent message to durable/non-durable queues must cause messages to be enqueued", 2, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted. + */ + public void testStoreEnqueuesCausesExceptions() throws Exception + { + _message = createTestMessage(true); + _queues = createTestBaseQueues(new boolean[] {true, true}); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new LocalTransaction(_transactionLog); + + try + { + _transaction.enqueue(_queues, _message, _action1); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertTrue("Rollback action must be fired", _action1.isRollbackActionFired()); + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertFalse("Post commit action must not be fired", _action1.isPostCommitActionFired()); + } + + /** + * Tests the dequeue of a non persistent message from a single non durable queue. + * Asserts that a store transaction has not been started. + */ + public void testDequeueFromNonDurableQueueOfNonPersistentMessage() throws Exception + { + _message = createTestMessage(false); + _queue = createTestAMQQueue(false); + + _transaction.dequeue(_queue, _message, _action1); + + assertEquals("Dequeue of non-persistent message must not cause message to be enqueued", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + + } + + /** + * Tests the dequeue of a persistent message from a single non durable queue. + * Asserts that a store transaction has not been started. + */ + public void testDequeueFromDurableQueueOfPersistentMessage() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.dequeue(_queue, _message, _action1); + + assertEquals("Dequeue of non-persistent message must cause message to be dequeued", 1, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted. + */ + public void testStoreDequeueCausesException() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new LocalTransaction(_transactionLog); + + try + { + _transaction.dequeue(_queue, _message, _action1); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertTrue("Rollback action must be fired", _action1.isRollbackActionFired()); + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertFalse("Post commit action must not be fired", _action1.isPostCommitActionFired()); + + } + + /** + * Tests the dequeue of a non persistent message from many non durable queues. + * Asserts that a store transaction has not been started. + */ + public void testDequeueFromManyNonDurableQueuesOfNonPersistentMessage() throws Exception + { + _queueEntries = createTestQueueEntries(new boolean[] {false, false, false}, new boolean[] {false, false, false}); + + _transaction.dequeue(_queueEntries, _action1); + + assertEquals("Dequeue of non-persistent messages must not cause message to be dequeued", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + + } + + /** + * Tests the dequeue of a persistent message from a many non durable queues. + * Asserts that a store transaction has not been started. + */ + public void testDequeueFromManyNonDurableQueuesOfPersistentMessage() throws Exception + { + _queueEntries = createTestQueueEntries(new boolean[] {false, false, false}, new boolean[] {true, true, true}); + + _transaction.dequeue(_queueEntries, _action1); + + assertEquals("Dequeue of persistent message from non-durable queues must not cause message to be enqueued", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the dequeue of a persistent message from many queues, some durable others not. + * Asserts that a store transaction has not been started. + */ + public void testDequeueFromDurableAndNonDurableQueuesOfPersistentMessage() throws Exception + { + // A transaction will exist owing to the 1st and 3rd. + _queueEntries = createTestQueueEntries(new boolean[] {true, false, true, true}, new boolean[] {true, true, true, false}); + + _transaction.dequeue(_queueEntries, _action1); + + assertEquals("Dequeue of persistent messages from durable/non-durable queues must cause messages to be dequeued", 2, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertNotFired(_action1); + } + + /** + * Tests the case where the store operation throws an exception. + * Asserts that the transaction is aborted. + */ + public void testStoreDequeuesCauseExceptions() throws Exception + { + // Transactions will exist owing to the 1st and 3rd queue entries in the collection + _queueEntries = createTestQueueEntries(new boolean[] {true}, new boolean[] {true}); + + _storeTransaction = createTestStoreTransaction(true); + _transactionLog = MockStoreTransaction.createTestTransactionLog(_storeTransaction); + _transaction = new LocalTransaction(_transactionLog); + + try + { + _transaction.dequeue(_queueEntries, _action1); + fail("Exception not thrown"); + } + catch (RuntimeException re) + { + // PASS + } + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertTrue("Rollback action must be fired", _action1.isRollbackActionFired()); + assertFalse("Post commit action must not be fired", _action1.isPostCommitActionFired()); + } + + /** + * Tests the add of a post-commit action. Unlike AutoCommitTranctions, the post transaction actions + * is added to a list to be fired on commit or rollback. + */ + public void testAddingPostCommitActionNotFiredImmediately() throws Exception + { + + _transaction.addPostTransactionAction(_action1); + + assertNotFired(_action1); + } + + + /** + * Tests committing a transaction without work accepted without error and without causing store + * enqueues or dequeues. + */ + public void testCommitNoWork() throws Exception + { + + _transaction.commit(); + + assertEquals("Unexpected number of store dequeues", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected number of store enqueues", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + } + + /** + * Tests rolling back a transaction without work accepted without error and without causing store + * enqueues or dequeues. + */ + public void testRollbackNoWork() throws Exception + { + + _transaction.rollback(); + + assertEquals("Unexpected number of store dequeues", 0, _storeTransaction.getNumberOfDequeuedMessages()); + assertEquals("Unexpected number of store enqueues", 0, _storeTransaction.getNumberOfEnqueuedMessages()); + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + } + + /** + * Tests the dequeuing of a message with a commit. Test ensures that the underlying store transaction is + * correctly controlled and the post commit action is fired. + */ + public void testCommitWork() throws Exception + { + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Post commit action must not be fired yet", _action1.isPostCommitActionFired()); + + _transaction.dequeue(_queue, _message, _action1); + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertFalse("Post commit action must not be fired yet", _action1.isPostCommitActionFired()); + + _transaction.commit(); + + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + assertTrue("Post commit action must be fired", _action1.isPostCommitActionFired()); + } + + /** + * Tests the dequeuing of a message with a rollback. Test ensures that the underlying store transaction is + * correctly controlled and the post rollback action is fired. + */ + public void testRollbackWork() throws Exception + { + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + + assertEquals("Unexpected transaction state", TransactionState.NOT_STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired yet", _action1.isRollbackActionFired()); + + _transaction.dequeue(_queue, _message, _action1); + + assertEquals("Unexpected transaction state", TransactionState.STARTED, _storeTransaction.getState()); + assertFalse("Rollback action must not be fired yet", _action1.isRollbackActionFired()); + + _transaction.rollback(); + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + assertTrue("Rollback action must be fired", _action1.isRollbackActionFired()); + + } + + /** + * Variation of testCommitWork with an additional post transaction action. + * + */ + public void testCommitWorkWithAdditionalPostAction() throws Exception + { + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.addPostTransactionAction(_action1); + _transaction.dequeue(_queue, _message, _action2); + _transaction.commit(); + + assertEquals("Unexpected transaction state", TransactionState.COMMITTED, _storeTransaction.getState()); + + assertTrue("Post commit action1 must be fired", _action1.isPostCommitActionFired()); + assertTrue("Post commit action2 must be fired", _action2.isPostCommitActionFired()); + + assertFalse("Rollback action1 must not be fired", _action1.isRollbackActionFired()); + assertFalse("Rollback action2 must not be fired", _action1.isRollbackActionFired()); + } + + /** + * Variation of testRollbackWork with an additional post transaction action. + * + */ + public void testRollbackWorkWithAdditionalPostAction() throws Exception + { + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.addPostTransactionAction(_action1); + _transaction.dequeue(_queue, _message, _action2); + _transaction.rollback(); + + assertEquals("Unexpected transaction state", TransactionState.ABORTED, _storeTransaction.getState()); + + assertFalse("Post commit action1 must not be fired", _action1.isPostCommitActionFired()); + assertFalse("Post commit action2 must not be fired", _action2.isPostCommitActionFired()); + + assertTrue("Rollback action1 must be fired", _action1.isRollbackActionFired()); + assertTrue("Rollback action2 must be fired", _action1.isRollbackActionFired()); + } + + public void testFirstEnqueueRecordsTransactionStartAndUpdateTime() throws Exception + { + assertEquals("Unexpected transaction start time before test", 0, _transaction.getTransactionStartTime()); + assertEquals("Unexpected transaction update time before test", 0, _transaction.getTransactionUpdateTime()); + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + long startTime = System.currentTimeMillis(); + _transaction.enqueue(_queue, _message, _action1); + + assertTrue("Transaction start time should have been recorded", _transaction.getTransactionStartTime() >= startTime); + assertEquals("Transaction update time should be the same as transaction start time", _transaction.getTransactionStartTime(), _transaction.getTransactionUpdateTime()); + } + + public void testSubsequentEnqueueAdvancesTransactionUpdateTimeOnly() throws Exception + { + assertEquals("Unexpected transaction start time before test", 0, _transaction.getTransactionStartTime()); + assertEquals("Unexpected transaction update time before test", 0, _transaction.getTransactionUpdateTime()); + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.enqueue(_queue, _message, _action1); + + final long transactionStartTimeAfterFirstEnqueue = _transaction.getTransactionStartTime(); + final long transactionUpdateTimeAfterFirstEnqueue = _transaction.getTransactionUpdateTime(); + + Thread.sleep(1); + _transaction.enqueue(_queue, _message, _action2); + + final long transactionStartTimeAfterSecondEnqueue = _transaction.getTransactionStartTime(); + final long transactionUpdateTimeAfterSecondEnqueue = _transaction.getTransactionUpdateTime(); + + assertEquals("Transaction start time after second enqueue should be unchanged", transactionStartTimeAfterFirstEnqueue, transactionStartTimeAfterSecondEnqueue); + assertTrue("Transaction update time after second enqueue should be greater than first update time", transactionUpdateTimeAfterSecondEnqueue > transactionUpdateTimeAfterFirstEnqueue); + } + + public void testFirstDequeueRecordsTransactionStartAndUpdateTime() throws Exception + { + assertEquals("Unexpected transaction start time before test", 0, _transaction.getTransactionStartTime()); + assertEquals("Unexpected transaction update time before test", 0, _transaction.getTransactionUpdateTime()); + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + long startTime = System.currentTimeMillis(); + _transaction.dequeue(_queue, _message, _action1); + + assertTrue("Transaction start time should have been recorded", _transaction.getTransactionStartTime() >= startTime); + assertEquals("Transaction update time should be the same as transaction start time", _transaction.getTransactionStartTime(), _transaction.getTransactionUpdateTime()); + } + + public void testMixedEnqueuesAndDequeuesAdvancesTransactionUpdateTimeOnly() throws Exception + { + assertEquals("Unexpected transaction start time before test", 0, _transaction.getTransactionStartTime()); + assertEquals("Unexpected transaction update time before test", 0, _transaction.getTransactionUpdateTime()); + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + _transaction.enqueue(_queue, _message, _action1); + + final long transactionStartTimeAfterFirstEnqueue = _transaction.getTransactionStartTime(); + final long transactionUpdateTimeAfterFirstEnqueue = _transaction.getTransactionUpdateTime(); + + Thread.sleep(1); + _transaction.dequeue(_queue, _message, _action2); + + final long transactionStartTimeAfterFirstDequeue = _transaction.getTransactionStartTime(); + final long transactionUpdateTimeAfterFirstDequeue = _transaction.getTransactionUpdateTime(); + + assertEquals("Transaction start time after first dequeue should be unchanged", transactionStartTimeAfterFirstEnqueue, transactionStartTimeAfterFirstDequeue); + assertTrue("Transaction update time after first dequeue should be greater than first update time", transactionUpdateTimeAfterFirstDequeue > transactionUpdateTimeAfterFirstEnqueue); + } + + public void testCommitResetsTransactionStartAndUpdateTime() throws Exception + { + assertEquals("Unexpected transaction start time before test", 0, _transaction.getTransactionStartTime()); + assertEquals("Unexpected transaction update time before test", 0, _transaction.getTransactionUpdateTime()); + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + long startTime = System.currentTimeMillis(); + _transaction.enqueue(_queue, _message, _action1); + + assertTrue(_transaction.getTransactionStartTime() >= startTime); + assertTrue(_transaction.getTransactionUpdateTime() >= startTime); + + _transaction.commit(); + + assertEquals("Transaction start time should be reset after commit", 0, _transaction.getTransactionStartTime()); + assertEquals("Transaction update time should be reset after commit", 0, _transaction.getTransactionUpdateTime()); + } + + public void testRollbackResetsTransactionStartAndUpdateTime() throws Exception + { + assertEquals("Unexpected transaction start time before test", 0, _transaction.getTransactionStartTime()); + assertEquals("Unexpected transaction update time before test", 0, _transaction.getTransactionUpdateTime()); + + _message = createTestMessage(true); + _queue = createTestAMQQueue(true); + + long startTime = System.currentTimeMillis(); + _transaction.enqueue(_queue, _message, _action1); + + assertTrue(_transaction.getTransactionStartTime() >= startTime); + assertTrue(_transaction.getTransactionUpdateTime() >= startTime); + + _transaction.rollback(); + + assertEquals("Transaction start time should be reset after rollback", 0, _transaction.getTransactionStartTime()); + assertEquals("Transaction update time should be reset after rollback", 0, _transaction.getTransactionUpdateTime()); + } + + private Collection createTestQueueEntries(boolean[] queueDurableFlags, boolean[] messagePersistentFlags) + { + Collection queueEntries = new ArrayList(); + + assertTrue("Boolean arrays must be the same length", queueDurableFlags.length == messagePersistentFlags.length); + + for(int i = 0; i < queueDurableFlags.length; i++) + { + final AMQQueue queue = createTestAMQQueue(queueDurableFlags[i]); + final ServerMessage message = createTestMessage(messagePersistentFlags[i]); + + queueEntries.add(new MockQueueEntry() + { + + @Override + public ServerMessage getMessage() + { + return message; + } + + @Override + public AMQQueue getQueue() + { + return queue; + } + + }); + } + + return queueEntries; + } + + private MockStoreTransaction createTestStoreTransaction(boolean throwException) + { + return new MockStoreTransaction(throwException); + } + + private List createTestBaseQueues(boolean[] durableFlags) + { + List queues = new ArrayList(); + for (boolean b: durableFlags) + { + queues.add(createTestAMQQueue(b)); + } + + return queues; + } + + private AMQQueue createTestAMQQueue(final boolean durable) + { + return new MockAMQQueue("mockQueue") + { + @Override + public boolean isDurable() + { + return durable; + } + + }; + } + + private ServerMessage createTestMessage(final boolean persistent) + { + return new MockServerMessage(persistent); + } + + private void assertNotFired(MockAction action) + { + assertFalse("Rollback action must not be fired", action.isRollbackActionFired()); + assertFalse("Post commit action must not be fired", action.isPostCommitActionFired()); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockAction.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockAction.java new file mode 100644 index 0000000000..15c135ea2c --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockAction.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.txn; + +import org.apache.qpid.server.txn.ServerTransaction.Action; + +/** + * Mock implementation of a ServerTranaction Action + * allowing its state to be observed. + * + */ +class MockAction implements Action +{ + private boolean _rollbackFired = false; + private boolean _postCommitFired = false; + + public void postCommit() + { + _postCommitFired = true; + } + + public void onRollback() + { + _rollbackFired = true; + } + + public boolean isRollbackActionFired() + { + return _rollbackFired; + } + + public boolean isPostCommitActionFired() + { + return _postCommitFired; + } +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockServerMessage.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockServerMessage.java new file mode 100644 index 0000000000..aa5b555b3b --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockServerMessage.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.txn; + +import org.apache.commons.lang.NotImplementedException; + +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.store.StoredMessage; + +import java.nio.ByteBuffer; + +/** + * Mock Server Message allowing its persistent flag to be controlled from test. + */ +class MockServerMessage implements ServerMessage +{ + /** + * + */ + private final boolean persistent; + + /** + * @param persistent + */ + MockServerMessage(boolean persistent) + { + this.persistent = persistent; + } + + public boolean isPersistent() + { + return persistent; + } + + public MessageReference newReference() + { + throw new NotImplementedException(); + } + + public boolean isImmediate() + { + throw new NotImplementedException(); + } + + public long getSize() + { + throw new NotImplementedException(); + } + + public String getRoutingKey() + { + throw new NotImplementedException(); + } + + public AMQMessageHeader getMessageHeader() + { + throw new NotImplementedException(); + } + + public StoredMessage getStoredMessage() + { + throw new NotImplementedException(); + } + + public long getExpiration() + { + throw new NotImplementedException(); + } + + public int getContent(ByteBuffer buf, int offset) + { + throw new NotImplementedException(); + } + + + public ByteBuffer getContent(int offset, int size) + { + throw new NotImplementedException(); + } + + public long getArrivalTime() + { + throw new NotImplementedException(); + } + + public long getMessageNumber() + { + return 0L; + } +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockStoreTransaction.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockStoreTransaction.java new file mode 100644 index 0000000000..0221f3d509 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/txn/MockStoreTransaction.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.txn; + +import org.apache.commons.lang.NotImplementedException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.NullMessageStore; +import org.apache.qpid.server.store.StoreFuture; +import org.apache.qpid.server.store.Transaction; +import org.apache.qpid.server.store.TransactionLogResource; + +/** + * Mock implementation of a (Store) Transaction allow its state to be observed. + * Also provide a factory method to produce TestTransactionLog objects suitable + * for unit test use. + * + */ +class MockStoreTransaction implements Transaction +{ + enum TransactionState {NOT_STARTED, STARTED, COMMITTED, ABORTED}; + + private TransactionState _state = TransactionState.NOT_STARTED; + + private int _numberOfEnqueuedMessages = 0; + private int _numberOfDequeuedMessages = 0; + private boolean _throwExceptionOnQueueOp; + + public MockStoreTransaction(boolean throwExceptionOnQueueOp) + { + _throwExceptionOnQueueOp = throwExceptionOnQueueOp; + } + + public void setState(TransactionState state) + { + _state = state; + } + + public TransactionState getState() + { + return _state; + } + + public void enqueueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException + { + if (_throwExceptionOnQueueOp) + { + + throw new AMQStoreException("Mocked exception"); + } + + _numberOfEnqueuedMessages++; + } + + public int getNumberOfDequeuedMessages() + { + return _numberOfDequeuedMessages; + } + + public int getNumberOfEnqueuedMessages() + { + return _numberOfEnqueuedMessages; + } + + public void dequeueMessage(TransactionLogResource queue, EnqueableMessage message) throws AMQStoreException + { + if (_throwExceptionOnQueueOp) + { + throw new AMQStoreException("Mocked exception"); + } + + _numberOfDequeuedMessages++; + } + + public void commitTran() throws AMQStoreException + { + _state = TransactionState.COMMITTED; + } + + public StoreFuture commitTranAsync() throws AMQStoreException + { + throw new NotImplementedException(); + } + + public void abortTran() throws AMQStoreException + { + _state = TransactionState.ABORTED; + } + + public void removeXid(long format, byte[] globalId, byte[] branchId) + { + } + + public void recordXid(long format, byte[] globalId, byte[] branchId, Record[] enqueues, Record[] dequeues) + { + } + + public static MessageStore createTestTransactionLog(final MockStoreTransaction storeTransaction) + { + return new NullMessageStore() + { + @Override + public Transaction newTransaction() + { + storeTransaction.setState(TransactionState.STARTED); + return storeTransaction; + } + + @Override + public String getStoreType() + { + return "TEST"; + } + }; + } +} \ No newline at end of file diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/BrokerTestHelper.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/BrokerTestHelper.java new file mode 100644 index 0000000000..cb1fc2737d --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/BrokerTestHelper.java @@ -0,0 +1,190 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.server.util; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.SocketAddress; +import java.util.Collections; +import java.util.UUID; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.configuration.store.JsonConfigurationEntryStore; +import org.apache.qpid.server.exchange.DefaultExchangeFactory; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.SystemOutMessageLogger; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.logging.actors.TestLogActor; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.model.UUIDGenerator; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.SimpleAMQQueue; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.SubjectCreator; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.TestableMemoryMessageStore; +import org.apache.qpid.server.virtualhost.StandardVirtualHostFactory; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.plugin.VirtualHostFactory; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public class BrokerTestHelper +{ + + protected static final String BROKER_STORE_CLASS_NAME_KEY = "brokerstore.class.name"; + protected static final String JSON_BROKER_STORE_CLASS_NAME = JsonConfigurationEntryStore.class.getName(); + + public static Broker createBrokerMock() + { + SubjectCreator subjectCreator = mock(SubjectCreator.class); + when(subjectCreator.getMechanisms()).thenReturn(""); + Broker broker = mock(Broker.class); + when(broker.getAttribute(Broker.CONNECTION_SESSION_COUNT_LIMIT)).thenReturn(1); + when(broker.getAttribute(Broker.CONNECTION_CLOSE_WHEN_NO_ROUTE)).thenReturn(false); + when(broker.getAttribute(Broker.VIRTUALHOST_HOUSEKEEPING_CHECK_PERIOD)).thenReturn(10000l); + when(broker.getId()).thenReturn(UUID.randomUUID()); + when(broker.getSubjectCreator(any(SocketAddress.class))).thenReturn(subjectCreator); + RootMessageLogger rootMessageLogger = CurrentActor.get().getRootMessageLogger(); + when(broker.getRootMessageLogger()).thenReturn(rootMessageLogger); + when(broker.getVirtualHostRegistry()).thenReturn(new VirtualHostRegistry()); + when(broker.getSecurityManager()).thenReturn(new SecurityManager(mock(Broker.class), false)); + GenericActor.setDefaultMessageLogger(rootMessageLogger); + return broker; + } + + public static void setUp() + { + CurrentActor.set(new TestLogActor(new SystemOutMessageLogger())); + } + + public static void tearDown() + { + CurrentActor.remove(); + } + + public static VirtualHost createVirtualHost(VirtualHostConfiguration virtualHostConfiguration, VirtualHostRegistry virtualHostRegistry) + throws Exception + { + return createVirtualHost(virtualHostConfiguration, virtualHostRegistry, mock(org.apache.qpid.server.model.VirtualHost.class)); + } + + public static VirtualHost createVirtualHost(VirtualHostConfiguration virtualHostConfiguration, VirtualHostRegistry virtualHostRegistry, org.apache.qpid.server.model.VirtualHost modelVHost) + throws Exception + { + StatisticsGatherer statisticsGatherer = mock(StatisticsGatherer.class); + final VirtualHostFactory factory = + virtualHostConfiguration == null ? new StandardVirtualHostFactory() + : VirtualHostFactory.FACTORIES.get(virtualHostConfiguration.getType()); + VirtualHost host = factory.createVirtualHost(virtualHostRegistry, + statisticsGatherer, + new SecurityManager(mock(Broker.class), false), + virtualHostConfiguration, + modelVHost); + if(virtualHostRegistry != null) + { + virtualHostRegistry.registerVirtualHost(host); + } + return host; + } + + public static VirtualHost createVirtualHost(VirtualHostConfiguration virtualHostConfiguration) throws Exception + { + return createVirtualHost(virtualHostConfiguration, null); + } + + public static VirtualHost createVirtualHost(String name, VirtualHostRegistry virtualHostRegistry) throws Exception + { + VirtualHostConfiguration vhostConfig = createVirtualHostConfiguration(name); + return createVirtualHost(vhostConfig, virtualHostRegistry); + } + + public static VirtualHost createVirtualHost(String name) throws Exception + { + VirtualHostConfiguration configuration = createVirtualHostConfiguration(name); + return createVirtualHost(configuration); + } + + private static VirtualHostConfiguration createVirtualHostConfiguration(String name) throws ConfigurationException + { + VirtualHostConfiguration vhostConfig = new VirtualHostConfiguration(name, new PropertiesConfiguration(), createBrokerMock()); + vhostConfig.setMessageStoreClass(TestableMemoryMessageStore.class.getName()); + return vhostConfig; + } + + public static AMQSessionModel createSession(int channelId, AMQConnectionModel connection) throws AMQException + { + AMQSessionModel session = mock(AMQSessionModel.class); + when(session.getConnectionModel()).thenReturn(connection); + when(session.getChannelId()).thenReturn(channelId); + return session; + } + + public static AMQSessionModel createSession(int channelId) throws Exception + { + AMQConnectionModel session = createConnection(); + return createSession(channelId, session); + } + + public static AMQSessionModel createSession() throws Exception + { + return createSession(1); + } + + public static AMQConnectionModel createConnection() throws Exception + { + return createConnection("test"); + } + + public static AMQConnectionModel createConnection(String hostName) throws Exception + { + VirtualHost virtualHost = createVirtualHost(hostName); + AMQConnectionModel connection = mock(AMQConnectionModel.class); + return connection; + } + + public static Exchange createExchange(String hostName) throws Exception + { + SecurityManager securityManager = new SecurityManager(mock(Broker.class), false); + VirtualHost virtualHost = mock(VirtualHost.class); + when(virtualHost.getName()).thenReturn(hostName); + when(virtualHost.getSecurityManager()).thenReturn(securityManager); + DefaultExchangeFactory factory = new DefaultExchangeFactory(virtualHost); + return factory.createExchange("amp.direct", "direct", false, false); + } + + public static SimpleAMQQueue createQueue(String queueName, VirtualHost virtualHost) throws AMQException + { + SimpleAMQQueue queue = (SimpleAMQQueue) virtualHost.createQueue(UUIDGenerator.generateRandomUUID(), queueName, false, null, + false, false, false, Collections.emptyMap()); + return queue; + } + + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/MapJsonSerializerTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/MapJsonSerializerTest.java new file mode 100644 index 0000000000..56567523df --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/MapJsonSerializerTest.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.util; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +public class MapJsonSerializerTest extends TestCase +{ + private MapJsonSerializer _serializer; + + protected void setUp() throws Exception + { + super.setUp(); + _serializer = new MapJsonSerializer(); + + } + + public void testSerializeDeserialize() + { + Map testMap = new HashMap(); + testMap.put("string", "Test String"); + testMap.put("integer", new Integer(10)); + testMap.put("long", new Long(Long.MAX_VALUE)); + testMap.put("boolean", Boolean.TRUE); + + String jsonString = _serializer.serialize(testMap); + Map deserializedMap = _serializer.deserialize(jsonString); + + assertEquals(deserializedMap, testMap); + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/StringUtilTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/StringUtilTest.java new file mode 100644 index 0000000000..29dc54a8f8 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/util/StringUtilTest.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.util; + +import org.apache.qpid.server.util.StringUtil; +import org.apache.qpid.test.utils.QpidTestCase; + +public class StringUtilTest extends QpidTestCase +{ + private StringUtil _util; + + @Override + public void setUp() throws Exception + { + super.setUp(); + _util = new StringUtil(); + } + + public void testRandomAlphaNumericStringInt() + { + String password = _util.randomAlphaNumericString(10); + assertEquals("Unexpected password string length", 10, password.length()); + assertCharacters(password); + } + + private void assertCharacters(String password) + { + String numbers = "0123456789"; + String letters = "abcdefghijklmnopqrstuvwxwy"; + String others = "_-"; + String expectedCharacters = (numbers + letters + letters.toUpperCase() + others); + char[] chars = password.toCharArray(); + for (int i = 0; i < chars.length; i++) + { + char ch = chars[i]; + assertTrue("Unexpected character " + ch, expectedCharacters.indexOf(ch) != -1); + } + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/DurableConfigurationRecovererTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/DurableConfigurationRecovererTest.java new file mode 100644 index 0000000000..987a541d05 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/DurableConfigurationRecovererTest.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.virtualhost; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.configuration.IllegalConfigurationException; +import org.apache.qpid.server.exchange.DirectExchange; +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.HeadersExchange; +import org.apache.qpid.server.exchange.TopicExchange; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.model.Binding; +import org.apache.qpid.server.model.Queue; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueFactory; +import org.apache.qpid.server.store.ConfiguredObjectRecord; +import org.apache.qpid.server.store.DurableConfigurationRecoverer; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.DurableConfiguredObjectRecoverer; +import org.apache.qpid.test.utils.QpidTestCase; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import static org.apache.qpid.server.model.VirtualHost.CURRENT_CONFIG_VERSION; + +public class DurableConfigurationRecovererTest extends QpidTestCase +{ + private static final UUID QUEUE_ID = new UUID(0,0); + private static final UUID TOPIC_EXCHANGE_ID = new UUID(0,1); + private static final UUID DIRECT_EXCHANGE_ID = new UUID(0,2); + private static final String CUSTOM_EXCHANGE_NAME = "customExchange"; + + private DurableConfigurationRecoverer _durableConfigurationRecoverer; + private Exchange _directExchange; + private Exchange _topicExchange; + private VirtualHost _vhost; + private DurableConfigurationStore _store; + private ExchangeFactory _exchangeFactory; + private ExchangeRegistry _exchangeRegistry; + private QueueFactory _queueFactory; + + @Override + public void setUp() throws Exception + { + super.setUp(); + + + _directExchange = mock(Exchange.class); + when(_directExchange.getType()).thenReturn(DirectExchange.TYPE); + + + _topicExchange = mock(Exchange.class); + when(_topicExchange.getType()).thenReturn(TopicExchange.TYPE); + + AMQQueue queue = mock(AMQQueue.class); + + _vhost = mock(VirtualHost.class); + + _exchangeRegistry = mock(ExchangeRegistry.class); + when(_exchangeRegistry.getExchange(eq(DIRECT_EXCHANGE_ID))).thenReturn(_directExchange); + when(_exchangeRegistry.getExchange(eq(TOPIC_EXCHANGE_ID))).thenReturn(_topicExchange); + + when(_vhost.getQueue(eq(QUEUE_ID))).thenReturn(queue); + + final ArgumentCaptor registeredExchange = ArgumentCaptor.forClass(Exchange.class); + doAnswer(new Answer() + { + + @Override + public Object answer(final InvocationOnMock invocation) throws Throwable + { + Exchange exchange = registeredExchange.getValue(); + when(_exchangeRegistry.getExchange(eq(exchange.getId()))).thenReturn(exchange); + when(_exchangeRegistry.getExchange(eq(exchange.getName()))).thenReturn(exchange); + return null; + } + }).when(_exchangeRegistry).registerExchange(registeredExchange.capture()); + + + + final ArgumentCaptor idArg = ArgumentCaptor.forClass(UUID.class); + final ArgumentCaptor queueArg = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor argsArg = ArgumentCaptor.forClass(Map.class); + + _queueFactory = mock(QueueFactory.class); + + when(_queueFactory.restoreQueue(idArg.capture(), queueArg.capture(), + anyString(), anyBoolean(), anyBoolean(), anyBoolean(), argsArg.capture())).then( + new Answer() + { + + @Override + public Object answer(final InvocationOnMock invocation) throws Throwable + { + final AMQQueue queue = mock(AMQQueue.class); + + final String queueName = queueArg.getValue(); + final UUID queueId = idArg.getValue(); + + when(queue.getName()).thenReturn(queueName); + when(queue.getId()).thenReturn(queueId); + when(_vhost.getQueue(eq(queueName))).thenReturn(queue); + when(_vhost.getQueue(eq(queueId))).thenReturn(queue); + + final ArgumentCaptor altExchangeArg = ArgumentCaptor.forClass(Exchange.class); + doAnswer( + new Answer() + { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable + { + final Exchange value = altExchangeArg.getValue(); + when(queue.getAlternateExchange()).thenReturn(value); + return null; + } + } + ).when(queue).setAlternateExchange(altExchangeArg.capture()); + + Map args = argsArg.getValue(); + if(args.containsKey(Queue.ALTERNATE_EXCHANGE)) + { + final UUID exchangeId = UUID.fromString(args.get(Queue.ALTERNATE_EXCHANGE).toString()); + final Exchange exchange = _exchangeRegistry.getExchange(exchangeId); + queue.setAlternateExchange(exchange); + } + return queue; + } + }); + + _exchangeFactory = mock(ExchangeFactory.class); + + + DurableConfiguredObjectRecoverer[] recoverers = { + new QueueRecoverer(_vhost, _exchangeRegistry, _queueFactory), + new ExchangeRecoverer(_exchangeRegistry, _exchangeFactory), + new BindingRecoverer(_vhost, _exchangeRegistry) + }; + + final Map recovererMap= new HashMap(); + for(DurableConfiguredObjectRecoverer recoverer : recoverers) + { + recovererMap.put(recoverer.getType(), recoverer); + } + _durableConfigurationRecoverer = + new DurableConfigurationRecoverer(_vhost.getName(), recovererMap, + new DefaultUpgraderProvider(_vhost, _exchangeRegistry)); + + _store = mock(DurableConfigurationStore.class); + + CurrentActor.set(mock(LogActor.class)); + } + + public void testUpgradeEmptyStore() throws Exception + { + _durableConfigurationRecoverer.beginConfigurationRecovery(_store, 0); + assertEquals("Did not upgrade to the expected version", + CURRENT_CONFIG_VERSION, + _durableConfigurationRecoverer.completeConfigurationRecovery()); + } + + public void testUpgradeNewerStoreFails() throws Exception + { + try + { + _durableConfigurationRecoverer.beginConfigurationRecovery(_store, CURRENT_CONFIG_VERSION + 1); + _durableConfigurationRecoverer.completeConfigurationRecovery(); + fail("Should not be able to start when config model is newer than current"); + } + catch (IllegalStateException e) + { + // pass + } + } + + public void testUpgradeRemovesBindingsToNonTopicExchanges() throws Exception + { + + _durableConfigurationRecoverer.beginConfigurationRecovery(_store, 0); + + _durableConfigurationRecoverer.configuredObject(new UUID(1, 0), + "org.apache.qpid.server.model.Binding", + createBinding("key", + DIRECT_EXCHANGE_ID, + QUEUE_ID, + "x-filter-jms-selector", + "wibble")); + + final ConfiguredObjectRecord[] expected = { + new ConfiguredObjectRecord(new UUID(1, 0), "Binding", + createBinding("key", DIRECT_EXCHANGE_ID, QUEUE_ID)) + }; + + verifyCorrectUpdates(expected); + + _durableConfigurationRecoverer.completeConfigurationRecovery(); + } + + + + public void testUpgradeOnlyRemovesSelectorBindings() throws Exception + { + + _durableConfigurationRecoverer.beginConfigurationRecovery(_store, 0); + + _durableConfigurationRecoverer.configuredObject(new UUID(1, 0), + "org.apache.qpid.server.model.Binding", + createBinding("key", + DIRECT_EXCHANGE_ID, + QUEUE_ID, + "x-filter-jms-selector", + "wibble", + "not-a-selector", + "moo")); + + + final UUID customExchangeId = new UUID(3,0); + + _durableConfigurationRecoverer.configuredObject(new UUID(2, 0), + "org.apache.qpid.server.model.Binding", + createBinding("key", + customExchangeId, + QUEUE_ID, + "x-filter-jms-selector", + "wibble", + "not-a-selector", + "moo")); + + _durableConfigurationRecoverer.configuredObject(customExchangeId, + "org.apache.qpid.server.model.Exchange", + createExchange(CUSTOM_EXCHANGE_NAME, HeadersExchange.TYPE)); + + final Exchange customExchange = mock(Exchange.class); + + when(_exchangeFactory.restoreExchange(eq(customExchangeId), + eq(CUSTOM_EXCHANGE_NAME), + eq(HeadersExchange.TYPE.getType()), + anyBoolean())).thenReturn(customExchange); + + final ConfiguredObjectRecord[] expected = { + new ConfiguredObjectRecord(new UUID(1, 0), "org.apache.qpid.server.model.Binding", + createBinding("key", DIRECT_EXCHANGE_ID, QUEUE_ID, "not-a-selector", "moo")), + new ConfiguredObjectRecord(new UUID(2, 0), "org.apache.qpid.server.model.Binding", + createBinding("key", customExchangeId, QUEUE_ID, "not-a-selector", "moo")) + }; + + verifyCorrectUpdates(expected); + + _durableConfigurationRecoverer.completeConfigurationRecovery(); + } + + + public void testUpgradeKeepsBindingsToTopicExchanges() throws Exception + { + + _durableConfigurationRecoverer.beginConfigurationRecovery(_store, 0); + + _durableConfigurationRecoverer.configuredObject(new UUID(1, 0), + "org.apache.qpid.server.model.Binding", + createBinding("key", + TOPIC_EXCHANGE_ID, + QUEUE_ID, + "x-filter-jms-selector", + "wibble")); + + final ConfiguredObjectRecord[] expected = { + new ConfiguredObjectRecord(new UUID(1, 0), "Binding", + createBinding("key", TOPIC_EXCHANGE_ID, QUEUE_ID, "x-filter-jms-selector", "wibble")) + }; + + verifyCorrectUpdates(expected); + + _durableConfigurationRecoverer.completeConfigurationRecovery(); + } + + public void testUpgradeDoesNotRecur() throws Exception + { + + _durableConfigurationRecoverer.beginConfigurationRecovery(_store, 2); + + _durableConfigurationRecoverer.configuredObject(new UUID(1, 0), + "Binding", + createBinding("key", + DIRECT_EXCHANGE_ID, + QUEUE_ID, + "x-filter-jms-selector", + "wibble")); + + doThrow(new RuntimeException("Update Should not be called")).when(_store).update(any(ConfiguredObjectRecord[].class)); + + _durableConfigurationRecoverer.completeConfigurationRecovery(); + } + + public void testFailsWithUnresolvedObjects() + { + _durableConfigurationRecoverer.beginConfigurationRecovery(_store, 2); + + + _durableConfigurationRecoverer.configuredObject(new UUID(1, 0), + "Binding", + createBinding("key", + new UUID(3,0), + QUEUE_ID, + "x-filter-jms-selector", + "wibble")); + + try + { + _durableConfigurationRecoverer.completeConfigurationRecovery(); + fail("Expected resolution to fail due to unknown object"); + } + catch(IllegalConfigurationException e) + { + assertEquals("Durable configuration has unresolved dependencies", e.getMessage()); + } + + } + + public void testFailsWithUnknownObjectType() + { + _durableConfigurationRecoverer.beginConfigurationRecovery(_store, 2); + + + try + { + final Map emptyArguments = Collections.emptyMap(); + _durableConfigurationRecoverer.configuredObject(new UUID(1, 0), + "Wibble", emptyArguments); + _durableConfigurationRecoverer.completeConfigurationRecovery(); + fail("Expected resolution to fail due to unknown object type"); + } + catch(IllegalConfigurationException e) + { + assertEquals("Unkown type for configured object: Wibble", e.getMessage()); + } + + + } + + public void testRecoveryOfQueueAlternateExchange() throws Exception + { + + final UUID queueId = new UUID(1, 0); + final UUID exchangeId = new UUID(2, 0); + + final Exchange customExchange = mock(Exchange.class); + + when(customExchange.getId()).thenReturn(exchangeId); + when(customExchange.getName()).thenReturn(CUSTOM_EXCHANGE_NAME); + + when(_exchangeFactory.restoreExchange(eq(exchangeId), + eq(CUSTOM_EXCHANGE_NAME), + eq(HeadersExchange.TYPE.getType()), + anyBoolean())).thenReturn(customExchange); + + _durableConfigurationRecoverer.beginConfigurationRecovery(_store, 2); + + _durableConfigurationRecoverer.configuredObject(queueId, Queue.class.getSimpleName(), + createQueue("testQueue", exchangeId)); + _durableConfigurationRecoverer.configuredObject(exchangeId, + org.apache.qpid.server.model.Exchange.class.getSimpleName(), + createExchange(CUSTOM_EXCHANGE_NAME, HeadersExchange.TYPE)); + + _durableConfigurationRecoverer.completeConfigurationRecovery(); + + assertEquals(customExchange, _vhost.getQueue(queueId).getAlternateExchange()); + } + + private void verifyCorrectUpdates(final ConfiguredObjectRecord[] expected) throws AMQStoreException + { + doAnswer(new Answer() + { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable + { + Object[] args = invocation.getArguments(); + assertEquals("Updated records are not as expected", new HashSet(Arrays.asList( + expected)), new HashSet(Arrays.asList(args))); + + return null; + } + }).when(_store).update(any(ConfiguredObjectRecord[].class)); + } + + private Map createBinding(String bindingKey, UUID exchangeId, UUID queueId, String... args) + { + Map binding = new LinkedHashMap(); + + binding.put("name", bindingKey); + binding.put(Binding.EXCHANGE, exchangeId.toString()); + binding.put(Binding.QUEUE, queueId.toString()); + Map argumentMap = new LinkedHashMap(); + if(args != null && args.length != 0) + { + String key = null; + for(String arg : args) + { + if(key == null) + { + key = arg; + } + else + { + argumentMap.put(key, arg); + key = null; + } + } + } + binding.put(Binding.ARGUMENTS, argumentMap); + return binding; + } + + + private Map createExchange(String name, ExchangeType type) + { + Map exchange = new LinkedHashMap(); + + exchange.put(org.apache.qpid.server.model.Exchange.NAME, name); + exchange.put(org.apache.qpid.server.model.Exchange.TYPE, type.getType()); + + return exchange; + + } + + + private Map createQueue(String name, UUID alternateExchangeId) + { + Map queue = new LinkedHashMap(); + + queue.put(Queue.NAME, name); + if(alternateExchangeId != null) + { + queue.put(Queue.ALTERNATE_EXCHANGE, alternateExchangeId.toString()); + } + queue.put(Queue.EXCLUSIVE, false); + + return queue; + + } + +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/HouseKeepingTaskTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/HouseKeepingTaskTest.java new file mode 100644 index 0000000000..8b4a52bb79 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/HouseKeepingTaskTest.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.virtualhost; + +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.NullRootMessageLogger; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.TestLogActor; +import org.apache.qpid.test.utils.QpidTestCase; + +import java.util.concurrent.CountDownLatch; + +public class HouseKeepingTaskTest extends QpidTestCase +{ + /** + * Tests that the abstract HouseKeepingTask properly cleans up any LogActor + * it adds to the CurrentActor stack by verifying the CurrentActor set + * before task execution is the CurrentActor after execution. + */ + public void testCurrentActorStackBalance() throws Exception + { + //create and set a test actor + LogActor testActor = new TestLogActor(new NullRootMessageLogger()); + CurrentActor.set(testActor); + + //verify it is returned correctly before executing a HouseKeepingTask + assertEquals("Expected LogActor was not returned", testActor, CurrentActor.get()); + + final CountDownLatch latch = new CountDownLatch(1); + HouseKeepingTask testTask = new HouseKeepingTask(new MockVirtualHost("HouseKeepingTaskTestVhost")) + { + @Override + public void execute() + { + latch.countDown(); + } + }; + + //run the test HouseKeepingTask using the current Thread to influence its CurrentActor stack + testTask.run(); + + assertEquals("The expected LogActor was not returned, the CurrentActor stack has become unbalanced", + testActor, CurrentActor.get()); + assertEquals("HouseKeepingTask execute() method was not run", 0, latch.getCount()); + + //clean up the test actor + CurrentActor.remove(); + } + + public void testThreadNameIsSetForDurationOfTask() throws Exception + { + //create and set a test actor + LogActor testActor = new TestLogActor(new NullRootMessageLogger()); + CurrentActor.set(testActor); + + String originalThreadName = Thread.currentThread().getName(); + + String vhostName = "HouseKeepingTaskTestVhost"; + + String expectedThreadNameDuringExecution = vhostName + ":" + "ThreadNameRememberingTask"; + + ThreadNameRememberingTask testTask = new ThreadNameRememberingTask(new MockVirtualHost(vhostName)); + + testTask.run(); + + assertEquals("Thread name should have been set during execution", expectedThreadNameDuringExecution, testTask.getThreadNameDuringExecution()); + assertEquals("Thread name should have been reverted after task has run", originalThreadName, Thread.currentThread().getName()); + + //clean up the test actor + CurrentActor.remove(); + } + + + private static final class ThreadNameRememberingTask extends HouseKeepingTask + { + private String _threadNameDuringExecution; + + private ThreadNameRememberingTask(VirtualHost vhost) + { + super(vhost); + } + + @Override + public void execute() + { + _threadNameDuringExecution = Thread.currentThread().getName(); // store current thread name so we can assert it later + throw new RuntimeException("deliberate exception to check that thread name still gets reverted"); + } + + public String getThreadNameDuringExecution() + { + return _threadNameDuringExecution; + } + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/MockVirtualHost.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/MockVirtualHost.java new file mode 100644 index 0000000000..1ca7ff1b65 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/MockVirtualHost.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 java.util.Collection; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.connection.IConnectionRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.plugin.ExchangeType; +import org.apache.qpid.server.protocol.LinkRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.stats.StatisticsCounter; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.txn.DtxRegistry; + +import java.util.UUID; + +public class MockVirtualHost implements VirtualHost +{ + private String _name; + + public MockVirtualHost(String name) + { + _name = name; + } + + public void close() + { + + } + + @Override + public VirtualHostRegistry getVirtualHostRegistry() + { + return null; + } + + public AuthenticationManager getAuthenticationManager() + { + return null; + } + + public DtxRegistry getDtxRegistry() + { + return null; + } + + public VirtualHostConfiguration getConfiguration() + { + return null; + } + + public IConnectionRegistry getConnectionRegistry() + { + return null; + } + + public int getHouseKeepingActiveCount() + { + return 0; + } + + public long getHouseKeepingCompletedTaskCount() + { + return 0; + } + + public int getHouseKeepingPoolSize() + { + return 0; + } + + public long getHouseKeepingTaskCount() + { + return 0; + } + + public MessageStore getMessageStore() + { + return null; + } + + public DurableConfigurationStore getDurableConfigurationStore() + { + return null; + } + + public String getName() + { + return _name; + } + + public QueueRegistry getQueueRegistry() + { + return null; + } + + @Override + public AMQQueue getQueue(String name) + { + return null; + } + + @Override + public AMQQueue getQueue(UUID id) + { + return null; + } + + @Override + public Collection getQueues() + { + return null; + } + + @Override + public int removeQueue(AMQQueue queue) throws AMQException + { + return 0; + } + + @Override + public AMQQueue createQueue(UUID id, + String queueName, + boolean durable, + String owner, + boolean autoDelete, + boolean exclusive, + boolean deleteOnNoConsumer, + Map arguments) throws AMQException + { + return null; + } + + @Override + public Exchange createExchange(UUID id, + String exchange, + String type, + boolean durable, + boolean autoDelete, + String alternateExchange) throws AMQException + { + return null; + } + + @Override + public void removeExchange(Exchange exchange, boolean force) throws AMQException + { + } + + @Override + public Exchange getExchange(String name) + { + return null; + } + + @Override + public Exchange getExchange(UUID id) + { + return null; + } + + @Override + public Exchange getDefaultExchange() + { + return null; + } + + @Override + public Collection getExchanges() + { + return null; + } + + @Override + public Collection> getExchangeTypes() + { + return null; + } + + public SecurityManager getSecurityManager() + { + return null; + } + + @Override + public void addVirtualHostListener(VirtualHostListener listener) + { + } + + public LinkRegistry getLinkRegistry(String remoteContainerId) + { + return null; + } + + public ScheduledFuture scheduleTask(long delay, Runnable timeoutTask) + { + return null; + } + + public void scheduleHouseKeepingTask(long period, HouseKeepingTask task) + { + + } + + public void setHouseKeepingPoolSize(int newSize) + { + + } + + + public long getCreateTime() + { + return 0; + } + + public UUID getId() + { + return null; + } + + public boolean isDurable() + { + return false; + } + + public StatisticsCounter getDataDeliveryStatistics() + { + return null; + } + + public StatisticsCounter getDataReceiptStatistics() + { + return null; + } + + public StatisticsCounter getMessageDeliveryStatistics() + { + return null; + } + + public StatisticsCounter getMessageReceiptStatistics() + { + return null; + } + + public void initialiseStatistics() + { + + } + + public void registerMessageDelivered(long messageSize) + { + + } + + public void registerMessageReceived(long messageSize, long timestamp) + { + + } + + public void resetStatistics() + { + + } + + public State getState() + { + return State.ACTIVE; + } + + public void block() + { + } + + public void unblock() + { + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/StandardVirtualHostTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/StandardVirtualHostTest.java new file mode 100644 index 0000000000..03cb483e40 --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/server/virtualhost/StandardVirtualHostTest.java @@ -0,0 +1,376 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * 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 static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; + +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.model.Broker; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.TestMemoryMessageStore; +import org.apache.qpid.server.util.BrokerTestHelper; +import org.apache.qpid.test.utils.QpidTestCase; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class StandardVirtualHostTest extends QpidTestCase +{ + private VirtualHostRegistry _virtualHostRegistry; + + @Override + public void setUp() throws Exception + { + super.setUp(); + BrokerTestHelper.setUp(); + } + + @Override + public void tearDown() throws Exception + { + try + { + if (_virtualHostRegistry != null) + { + _virtualHostRegistry.close(); + } + } + finally + { + BrokerTestHelper.tearDown(); + super.tearDown(); + } + + } + + /** + * Tests that custom routing keys for the queue specified in the configuration + * file are correctly bound to the exchange (in addition to the queue name) + */ + public void testSpecifyingCustomBindings() throws Exception + { + customBindingTestImpl(new String[]{"custom1","custom2"}); + } + + /** + * Tests that a queue specified in the configuration file to be bound to a + * specified(non-default) direct exchange is a correctly bound to the exchange + * and the default exchange using the queue name. + */ + public void testQueueSpecifiedInConfigurationIsBoundToDefaultExchange() throws Exception + { + customBindingTestImpl(new String[0]); + } + + /** + * Tests that specifying custom routing keys for a queue in the configuration file results in failure + * to create the vhost (since this is illegal, only queue names are used with the default exchange) + */ + public void testSpecifyingCustomBindingForDefaultExchangeThrowsException() throws Exception + { + final String queueName = getName(); + final String customBinding = "custom-binding"; + File config = writeConfigFile(queueName, queueName, null, false, new String[]{customBinding}); + + try + { + createVirtualHost(queueName, config); + fail("virtualhost creation should have failed due to illegal configuration"); + } + catch (ConfigurationException e) + { + assertEquals("Illegal attempt to bind queue '" + queueName + "' to the default exchange with a key other than the queue name: " + customBinding, e.getMessage()); + } + } + + public void testVirtualHostBecomesActive() throws Exception + { + File config = writeConfigFile(getName(), getName(), getName() +".direct", false, new String[0]); + VirtualHost vhost = createVirtualHost(getName(), config); + assertNotNull(vhost); + assertEquals(State.ACTIVE, vhost.getState()); + } + + public void testVirtualHostHavingStoreSetAsTypeBecomesActive() throws Exception + { + String virtualHostName = getName(); + VirtualHost host = createVirtualHostUsingStoreType(virtualHostName); + assertNotNull(host); + assertEquals(State.ACTIVE, host.getState()); + } + + public void testVirtualHostBecomesStoppedOnClose() throws Exception + { + File config = writeConfigFile(getName(), getName(), getName() +".direct", false, new String[0]); + VirtualHost vhost = createVirtualHost(getName(), config); + assertNotNull(vhost); + assertEquals(State.ACTIVE, vhost.getState()); + vhost.close(); + assertEquals(State.STOPPED, vhost.getState()); + assertEquals(0, vhost.getHouseKeepingActiveCount()); + } + + public void testVirtualHostHavingStoreSetAsTypeBecomesStoppedOnClose() throws Exception + { + String virtualHostName = getName(); + VirtualHost host = createVirtualHostUsingStoreType(virtualHostName); + assertNotNull(host); + assertEquals(State.ACTIVE, host.getState()); + host.close(); + assertEquals(State.STOPPED, host.getState()); + assertEquals(0, host.getHouseKeepingActiveCount()); + } + + /** + * Tests that specifying an unknown exchange to bind the queue to results in failure to create the vhost + */ + public void testSpecifyingUnknownExchangeThrowsException() throws Exception + { + final String queueName = getName(); + final String exchangeName = "made-up-exchange"; + File config = writeConfigFile(queueName, queueName, exchangeName, true, new String[0]); + + try + { + createVirtualHost(queueName, config); + fail("virtualhost creation should have failed due to illegal configuration"); + } + catch (ConfigurationException e) + { + assertEquals("Attempt to bind queue '" + queueName + "' to unknown exchange:" + exchangeName, e.getMessage()); + } + } + + public void testCreateVirtualHostWithoutConfigurationInConfigFile() throws Exception + { + File config = writeConfigFile(getName(), getName(), getName() +".direct", false, new String[0]); + String hostName = getName() + "-not-existing"; + try + { + createVirtualHost(hostName, config); + fail("virtualhost creation should have failed due to illegal configuration"); + } + catch (RuntimeException e) + { + assertEquals("No configuration found for virtual host '" + hostName + "' in " + config.getAbsolutePath(), e.getMessage()); + } + } + + public void testBindingArguments() throws Exception + { + String exchangeName = getName() +".direct"; + String vhostName = getName(); + String queueName = getName(); + + Map bindingArguments = new HashMap(); + bindingArguments.put("ping", new String[]{"x-filter-jms-selector=select=1", "x-qpid-no-local"}); + bindingArguments.put("pong", new String[]{"x-filter-jms-selector=select='pong'"}); + File config = writeConfigFile(vhostName, queueName, exchangeName, false, new String[]{"ping","pong"}, bindingArguments); + VirtualHost vhost = createVirtualHost(vhostName, config); + + Exchange exch = vhost.getExchange(getName() +".direct"); + Collection bindings = exch.getBindings(); + assertNotNull("Bindings cannot be null", bindings); + assertEquals("Unexpected number of bindings", 3, bindings.size()); + + boolean foundPong = false; + boolean foundPing = false; + for (Binding binding : bindings) + { + String qn = binding.getQueue().getName(); + assertEquals("Unexpected queue name", getName(), qn); + Map arguments = binding.getArguments(); + + if ("ping".equals(binding.getBindingKey())) + { + foundPing = true; + assertEquals("Unexpected number of binding arguments for ping", 2, arguments.size()); + assertEquals("Unexpected x-filter-jms-selector for ping", "select=1", arguments.get("x-filter-jms-selector")); + assertTrue("Unexpected x-qpid-no-local for ping", arguments.containsKey("x-qpid-no-local")); + } + else if ("pong".equals(binding.getBindingKey())) + { + foundPong = true; + assertEquals("Unexpected number of binding arguments for pong", 1, arguments.size()); + assertEquals("Unexpected x-filter-jms-selector for pong", "select='pong'", arguments.get("x-filter-jms-selector")); + } + } + + assertTrue("Pong binding is not found", foundPong); + assertTrue("Ping binding is not found", foundPing); + } + + private void customBindingTestImpl(final String[] routingKeys) throws Exception + { + String exchangeName = getName() +".direct"; + String vhostName = getName(); + String queueName = getName(); + + File config = writeConfigFile(vhostName, queueName, exchangeName, false, routingKeys); + VirtualHost vhost = createVirtualHost(vhostName, config); + assertNotNull("virtualhost should exist", vhost); + + AMQQueue queue = vhost.getQueue(queueName); + assertNotNull("queue should exist", queue); + + Exchange defaultExch = vhost.getDefaultExchange(); + assertTrue("queue should have been bound to default exchange with its name", defaultExch.isBound(queueName, queue)); + + Exchange exch = vhost.getExchange(exchangeName); + assertTrue("queue should have been bound to " + exchangeName + " with its name", exch.isBound(queueName, queue)); + + for(String key: routingKeys) + { + assertTrue("queue should have been bound to " + exchangeName + " with key " + key, exch.isBound(key, queue)); + } + } + + + private VirtualHost createVirtualHost(String vhostName, File config) throws Exception + { + Broker broker = BrokerTestHelper.createBrokerMock(); + _virtualHostRegistry = broker.getVirtualHostRegistry(); + + VirtualHostConfiguration configuration = new VirtualHostConfiguration(vhostName, config, broker); + VirtualHost host = new StandardVirtualHostFactory().createVirtualHost(_virtualHostRegistry, mock(StatisticsGatherer.class), new SecurityManager(mock(Broker.class), false), configuration, + mock(org.apache.qpid.server.model.VirtualHost.class)); + _virtualHostRegistry.registerVirtualHost(host); + + return host; + } + + /** + * Create a configuration file for testing virtualhost creation + * + * @param vhostName name of the virtualhost + * @param queueName name of the queue + * @param exchangeName name of a direct exchange to declare (unless dontDeclare = true) and bind the queue to (null = none) + * @param dontDeclare if true then dont declare the exchange, even if its name is non-null + * @param routingKeys routingKeys to bind the queue with (empty array = none) + * @return + */ + private File writeConfigFile(String vhostName, String queueName, String exchangeName, boolean dontDeclare, String[] routingKeys) + { + return writeConfigFile(vhostName, queueName, exchangeName, dontDeclare, routingKeys, null); + } + + private File writeConfigFile(String vhostName, String queueName, String exchangeName, boolean dontDeclare, String[] routingKeys, Map bindingArguments) + { + File tmpFile = null; + try + { + tmpFile = File.createTempFile(getName(), ".tmp"); + tmpFile.deleteOnExit(); + + FileWriter fstream = new FileWriter(tmpFile); + BufferedWriter writer = new BufferedWriter(fstream); + + //extra outer tag to please Commons Configuration + + writer.write(""); + writer.write(" " + vhostName + ""); + writer.write(" "); + writer.write(" " + vhostName + ""); + writer.write(" <" + vhostName + ">"); + writer.write(" " + StandardVirtualHostFactory.TYPE + ""); + writer.write(" "); + writer.write(" " + TestMemoryMessageStore.class.getName() + ""); + writer.write(" "); + if(exchangeName != null && !dontDeclare) + { + writer.write(" "); + writer.write(" "); + writer.write(" direct"); + writer.write(" " + exchangeName + ""); + writer.write(" "); + writer.write(" "); + } + writer.write(" "); + writer.write(" "); + writer.write(" " + queueName + ""); + writer.write(" <" + queueName + ">"); + if(exchangeName != null) + { + writer.write(" " + exchangeName + ""); + } + for(String routingKey: routingKeys) + { + writer.write(" " + routingKey + "\n"); + if (bindingArguments!= null && bindingArguments.containsKey(routingKey)) + { + writer.write(" <" + routingKey + ">\n"); + String[] arguments = (String[])bindingArguments.get(routingKey); + for (String argument : arguments) + { + writer.write(" " + argument + "\n"); + } + writer.write(" \n"); + } + } + writer.write(" "); + writer.write(" "); + writer.write(" "); + writer.write(" "); + writer.write(" "); + writer.write(""); + + writer.flush(); + writer.close(); + } + catch (IOException e) + { + fail("Unable to create virtualhost configuration"); + } + + return tmpFile; + } + + private VirtualHost createVirtualHostUsingStoreType(String virtualHostName) throws ConfigurationException, Exception + { + Broker broker = BrokerTestHelper.createBrokerMock(); + _virtualHostRegistry = broker.getVirtualHostRegistry(); + + Configuration config = new PropertiesConfiguration(); + VirtualHostConfiguration configuration = new VirtualHostConfiguration(virtualHostName, config, broker); + final org.apache.qpid.server.model.VirtualHost virtualHost = mock(org.apache.qpid.server.model.VirtualHost.class); + when(virtualHost.getAttribute(eq(org.apache.qpid.server.model.VirtualHost.STORE_TYPE))).thenReturn(TestMemoryMessageStore.TYPE); + VirtualHost host = new StandardVirtualHostFactory().createVirtualHost(_virtualHostRegistry, mock(StatisticsGatherer.class), new SecurityManager(mock(Broker.class), false), configuration, + virtualHost); + _virtualHostRegistry.registerVirtualHost(host); + return host; + } +} diff --git a/qpid/java/broker-core/src/test/java/org/apache/qpid/tools/security/PasswdTest.java b/qpid/java/broker-core/src/test/java/org/apache/qpid/tools/security/PasswdTest.java new file mode 100644 index 0000000000..b2a7234c8a --- /dev/null +++ b/qpid/java/broker-core/src/test/java/org/apache/qpid/tools/security/PasswdTest.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.tools.security; + +import junit.framework.TestCase; + +public class PasswdTest extends TestCase +{ + public void testUserGuestAndPasswordGuest() throws Exception + { + Passwd passwd = new Passwd(); + String output = passwd.getOutput("guest", "guest"); + assertEquals("guest:CE4DQ6BIb/BVMN9scFyLtA==", output); + } + + public void testUser1AndPasswordFoo() throws Exception + { + Passwd passwd = new Passwd(); + String output = passwd.getOutput("user1", "foo"); + assertEquals("user1:rL0Y20zC+Fzt72VPzMSk2A==", output); + } +} diff --git a/qpid/java/broker-core/src/test/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory b/qpid/java/broker-core/src/test/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory new file mode 100644 index 0000000000..9512fb8117 --- /dev/null +++ b/qpid/java/broker-core/src/test/resources/META-INF/services/org.apache.qpid.server.plugin.MessageStoreFactory @@ -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. +# +org.apache.qpid.server.store.TestMemoryMessageStoreFactory diff --git a/qpid/java/broker-core/src/velocity/java/org/apache/qpid/server/logging/GenerateLogMessages.java b/qpid/java/broker-core/src/velocity/java/org/apache/qpid/server/logging/GenerateLogMessages.java new file mode 100644 index 0000000000..a10d3b6a77 --- /dev/null +++ b/qpid/java/broker-core/src/velocity/java/org/apache/qpid/server/logging/GenerateLogMessages.java @@ -0,0 +1,532 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ + +package org.apache.qpid.server.logging; + +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; + +import java.io.File; +import java.io.FileWriter; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.ResourceBundle; + +public class GenerateLogMessages +{ + private boolean DEBUG = false; + private String _tmplDir; + private String _outputDir; + private List _logMessages = new LinkedList(); + private String _packageSource; + + public static void main(String[] args) + { + GenerateLogMessages generator = null; + try + { + generator = new GenerateLogMessages(args); + } + catch (IllegalAccessException iae) + { + // This occurs when args does not contain Template and output dirs. + System.exit(-1); + } + catch (Exception e) + { + //This is thrown by the Velocity Engine initialisation + e.printStackTrace(); + System.exit(-1); + } + + try + { + System.out.println("Running LogMessage Generator"); + generator.run(); + } + catch (InvalidTypeException e) + { + // This occurs when a type other than 'number' appears in the paramater config {0, number...}. + System.err.println(e.getMessage()); + System.exit(-1); + } + catch (Exception e) + { + e.printStackTrace(); + System.exit(-1); + } + } + + GenerateLogMessages(String[] args) throws Exception + { + processArgs(args); + + // We need the template and input files to run. + if (_tmplDir == null || _outputDir == null || _logMessages.size() == 0 || _packageSource == null) + { + showUsage(); + throw new IllegalAccessException(); + } + + // Initialise the Velocity Engine, Telling it where our macro lives + Properties props = new Properties(); + props.setProperty("file.resource.loader.path", _tmplDir); + Velocity.init(props); + } + + private void showUsage() + { + System.out.println("Broker LogMessageGenerator v.2.0"); + System.out.println("Usage: GenerateLogMessages: [-d] -t